From aabec2aea2a95ec0a386e563657d2196dfafcb89 Mon Sep 17 00:00:00 2001
From: ViperMaul <vipermaul@gmail.com>
Date: Tue, 21 Apr 2015 04:02:10 -0700
Subject: [PATCH 1/2] Converting Tabs to Spaces. Confirming UTF-8

---
 tools/make.py | 1690 +++++++++++++++++++++++++------------------------
 1 file changed, 846 insertions(+), 844 deletions(-)

diff --git a/tools/make.py b/tools/make.py
index 5146a666bb..aaaf6501c5 100644
--- a/tools/make.py
+++ b/tools/make.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python
 # vim: set fileencoding=utf-8 :
 
 # make.py
@@ -35,8 +35,8 @@ __version__ = "0.4"
 import sys
 
 if sys.version_info[0] == 2:
-	print("Python 3 is required.")
-	sys.exit(1)
+    print("Python 3 is required.")
+    sys.exit(1)
 
 import os
 import os.path
@@ -52,7 +52,7 @@ import time
 import re
 
 if sys.platform == "win32":
-	import winreg
+    import winreg
 
 ######## GLOBALS #########
 work_drive = ""
@@ -70,369 +70,371 @@ optionals_root = ""
 #   -1 -> Directory does not exist
 #   -2 -> General error (see stack traceback)
 def  get_directory_hash(directory):
-	directory_hash = hashlib.sha1()
-	if not os.path.exists (directory):
-		return -1
+    directory_hash = hashlib.sha1()
+    if not os.path.exists (directory):
+        return -1
 
-	try:
-		for root, dirs, files in os.walk(directory):
-			for names in files:
-				path = os.path.join(root, names)
-				try:
-					f = open(path, 'rb')
-				except:
-					# You can't open the file for some reason
-					f.close()
-					continue
+    try:
+        for root, dirs, files in os.walk(directory):
+            for names in files:
+                path = os.path.join(root, names)
+                try:
+                    f = open(path, 'rb')
+                except:
+                    # You can't open the file for some reason
+                    f.close()
+                    continue
 
-				while 1:
-					# Read file in as little chunks
-					buf = f.read(4096)
-					if not buf: break
-					new = hashlib.sha1(buf)
-					directory_hash.update(new.digest())
-				f.close()
+                while 1:
+                    # Read file in as little chunks
+                    buf = f.read(4096)
+                    if not buf: break
+                    new = hashlib.sha1(buf)
+                    directory_hash.update(new.digest())
+                f.close()
 
-	except:
-		# Print the stack traceback
-		traceback.print_exc()
-		return -2
+    except:
+        # Print the stack traceback
+        traceback.print_exc()
+        return -2
 
-	return directory_hash.hexdigest()
+    return directory_hash.hexdigest()
 
 # Copyright (c) André Burgaud
 # http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/
 if sys.platform == "win32":
-	from ctypes import windll, Structure, c_short, c_ushort, byref
+    from ctypes import windll, Structure, c_short, c_ushort, byref
 
-	SHORT = c_short
-	WORD = c_ushort
+    SHORT = c_short
+    WORD = c_ushort
 
-	class COORD(Structure):
-	  """struct in wincon.h."""
-	  _fields_ = [
-		("X", SHORT),
-		("Y", SHORT)]
+    class COORD(Structure):
+      """struct in wincon.h."""
+      _fields_ = [
+        ("X", SHORT),
+        ("Y", SHORT)]
 
-	class SMALL_RECT(Structure):
-	  """struct in wincon.h."""
-	  _fields_ = [
-		("Left", SHORT),
-		("Top", SHORT),
-		("Right", SHORT),
-		("Bottom", SHORT)]
+    class SMALL_RECT(Structure):
+      """struct in wincon.h."""
+      _fields_ = [
+        ("Left", SHORT),
+        ("Top", SHORT),
+        ("Right", SHORT),
+        ("Bottom", SHORT)]
 
-	class CONSOLE_SCREEN_BUFFER_INFO(Structure):
-	  """struct in wincon.h."""
-	  _fields_ = [
-		("dwSize", COORD),
-		("dwCursorPosition", COORD),
-		("wAttributes", WORD),
-		("srWindow", SMALL_RECT),
-		("dwMaximumWindowSize", COORD)]
+    class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+      """struct in wincon.h."""
+      _fields_ = [
+        ("dwSize", COORD),
+        ("dwCursorPosition", COORD),
+        ("wAttributes", WORD),
+        ("srWindow", SMALL_RECT),
+        ("dwMaximumWindowSize", COORD)]
 
-	# winbase.h
-	STD_INPUT_HANDLE = -10
-	STD_OUTPUT_HANDLE = -11
-	STD_ERROR_HANDLE = -12
+    # winbase.h
+    STD_INPUT_HANDLE = -10
+    STD_OUTPUT_HANDLE = -11
+    STD_ERROR_HANDLE = -12
 
-	# wincon.h
-	FOREGROUND_BLACK     = 0x0000
-	FOREGROUND_BLUE      = 0x0001
-	FOREGROUND_GREEN     = 0x0002
-	FOREGROUND_CYAN      = 0x0003
-	FOREGROUND_RED       = 0x0004
-	FOREGROUND_MAGENTA   = 0x0005
-	FOREGROUND_YELLOW    = 0x0006
-	FOREGROUND_GREY      = 0x0007
-	FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.
+    # wincon.h
+    FOREGROUND_BLACK     = 0x0000
+    FOREGROUND_BLUE      = 0x0001
+    FOREGROUND_GREEN     = 0x0002
+    FOREGROUND_CYAN      = 0x0003
+    FOREGROUND_RED       = 0x0004
+    FOREGROUND_MAGENTA   = 0x0005
+    FOREGROUND_YELLOW    = 0x0006
+    FOREGROUND_GREY      = 0x0007
+    FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.
 
-	BACKGROUND_BLACK     = 0x0000
-	BACKGROUND_BLUE      = 0x0010
-	BACKGROUND_GREEN     = 0x0020
-	BACKGROUND_CYAN      = 0x0030
-	BACKGROUND_RED       = 0x0040
-	BACKGROUND_MAGENTA   = 0x0050
-	BACKGROUND_YELLOW    = 0x0060
-	BACKGROUND_GREY      = 0x0070
-	BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
+    BACKGROUND_BLACK     = 0x0000
+    BACKGROUND_BLUE      = 0x0010
+    BACKGROUND_GREEN     = 0x0020
+    BACKGROUND_CYAN      = 0x0030
+    BACKGROUND_RED       = 0x0040
+    BACKGROUND_MAGENTA   = 0x0050
+    BACKGROUND_YELLOW    = 0x0060
+    BACKGROUND_GREY      = 0x0070
+    BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
 
-	stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
-	SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
-	GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+    stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
+    SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+    GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
 
-	def get_text_attr():
-	  """Returns the character attributes (colors) of the console screen
-	  buffer."""
-	  csbi = CONSOLE_SCREEN_BUFFER_INFO()
-	  GetConsoleScreenBufferInfo(stdout_handle, byref(csbi))
-	  return csbi.wAttributes
+    def get_text_attr():
+      """Returns the character attributes (colors) of the console screen
+      buffer."""
+      csbi = CONSOLE_SCREEN_BUFFER_INFO()
+      GetConsoleScreenBufferInfo(stdout_handle, byref(csbi))
+      return csbi.wAttributes
 
-	def set_text_attr(color):
-	  """Sets the character attributes (colors) of the console screen
-	  buffer. Color is a combination of foreground and background color,
-	  foreground and background intensity."""
-	  SetConsoleTextAttribute(stdout_handle, color)
+    def set_text_attr(color):
+      """Sets the character attributes (colors) of the console screen
+      buffer. Color is a combination of foreground and background color,
+      foreground and background intensity."""
+      SetConsoleTextAttribute(stdout_handle, color)
 ###############################################################################
 
 def find_bi_tools(work_drive):
-	"""Find BI tools."""
+    """Find BI tools."""
 
-	reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
-	try:
-		k = winreg.OpenKey(reg, r"Software\bohemia interactive\arma 3 tools")
-		arma3tools_path = winreg.QueryValueEx(k, "path")[0]
-		winreg.CloseKey(k)
-	except:
-		raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
+    reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
+    try:
+        k = winreg.OpenKey(reg, r"Software\bohemia interactive\arma 3 tools")
+        arma3tools_path = winreg.QueryValueEx(k, "path")[0]
+        winreg.CloseKey(k)
+    except:
+        raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
 
-	addonbuilder_path = os.path.join(arma3tools_path, "AddonBuilder", "AddonBuilder.exe")
-	dssignfile_path = os.path.join(arma3tools_path, "DSSignFile", "DSSignFile.exe")
-	dscreatekey_path = os.path.join(arma3tools_path, "DSSignFile", "DSCreateKey.exe")
-	cfgconvert_path = os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe")
+    addonbuilder_path = os.path.join(arma3tools_path, "AddonBuilder", "AddonBuilder.exe")
+    dssignfile_path = os.path.join(arma3tools_path, "DSSignFile", "DSSignFile.exe")
+    dscreatekey_path = os.path.join(arma3tools_path, "DSSignFile", "DSCreateKey.exe")
+    cfgconvert_path = os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe")
 
-	if os.path.isfile(addonbuilder_path) and os.path.isfile(dssignfile_path) and os.path.isfile(dscreatekey_path) and os.path.isfile(cfgconvert_path):
-		return [addonbuilder_path, dssignfile_path, dscreatekey_path, cfgconvert_path]
-	else:
-		raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
+    if os.path.isfile(addonbuilder_path) and os.path.isfile(dssignfile_path) and os.path.isfile(dscreatekey_path) and os.path.isfile(cfgconvert_path):
+        return [addonbuilder_path, dssignfile_path, dscreatekey_path, cfgconvert_path]
+    else:
+        raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
 
 def find_depbo_tools(regKey):
-	"""Use registry entries to find DePBO-based tools."""
-	stop = False
+    """Use registry entries to find DePBO-based tools."""
+    stop = False
 
-	if regKey == "HKCU":
-		reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
-		stop = True
-	else:
-		reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+    if regKey == "HKCU":
+        reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
+        stop = True
+    else:
+        reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
 
-	try:
-		try:
-			k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\pboProject")
-		except FileNotFoundError:
-			k = winreg.OpenKey(reg, r"Software\Mikero\pboProject")
-		try:
-			pboproject_path = winreg.QueryValueEx(k, "exe")[0]
-			winreg.CloseKey(k)
-			print("Found pboproject.")
-		except:
-			print_error("ERROR: Could not find pboProject.")
+    try:
+        try:
+            k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\pboProject")
+        except FileNotFoundError:
+            k = winreg.OpenKey(reg, r"Software\Mikero\pboProject")
+        try:
+            pboproject_path = winreg.QueryValueEx(k, "exe")[0]
+            winreg.CloseKey(k)
+            print("Found pboproject.")
+        except:
+            print_error("ERROR: Could not find pboProject.")
 
-		try:
-			k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\rapify")
-		except FileNotFoundError:
-			k = winreg.OpenKey(reg, r"Software\Mikero\rapify")
-		try:
-			rapify_path = winreg.QueryValueEx(k, "exe")[0]
-			winreg.CloseKey(k)
-			print("Found rapify.")
-		except:
-			print_error("Could not find rapify.")
+        try:
+            k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\rapify")
+        except FileNotFoundError:
+            k = winreg.OpenKey(reg, r"Software\Mikero\rapify")
+        try:
+            rapify_path = winreg.QueryValueEx(k, "exe")[0]
+            winreg.CloseKey(k)
+            print("Found rapify.")
+        except:
+            print_error("Could not find rapify.")
 
-		try:
-			k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\MakePbo")
-		except FileNotFoundError:
-			k = winreg.OpenKey(reg, r"Software\Mikero\MakePbo")
-		try:
-			makepbo_path = winreg.QueryValueEx(k, "exe")[0]
-			winreg.CloseKey(k)
-			print("Found makepbo.")
-		except:
-			print_error("Could not find makepbo.")
-	except:
-		if stop == True:
-			raise Exception("BadDePBO", "DePBO tools not installed correctly")
-		return -1
+        try:
+            k = winreg.OpenKey(reg, r"Software\Wow6432Node\Mikero\MakePbo")
+        except FileNotFoundError:
+            k = winreg.OpenKey(reg, r"Software\Mikero\MakePbo")
+        try:
+            makepbo_path = winreg.QueryValueEx(k, "exe")[0]
+            winreg.CloseKey(k)
+            print("Found makepbo.")
+        except:
+            print_error("Could not find makepbo.")
+    except:
+        if stop == True:
+            raise Exception("BadDePBO", "DePBO tools not installed correctly")
+        return -1
 
 
-	#Strip any quotations from the path due to a MikeRo tool bug which leaves a trailing space in some of its registry paths.
-	return [pboproject_path.strip('"'),rapify_path.strip('"'),makepbo_path.strip('"')]
+    #Strip any quotations from the path due to a MikeRo tool bug which leaves a trailing space in some of its registry paths.
+    return [pboproject_path.strip('"'),rapify_path.strip('"'),makepbo_path.strip('"')]
 
 def color(color):
-	"""Set the color. Works on Win32 and normal terminals."""
-	if sys.platform == "win32":
-		if color == "green":
-			set_text_attr(FOREGROUND_GREEN | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
-		elif color == "red":
-			set_text_attr(FOREGROUND_RED | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
-		elif color == "blue":
-			set_text_attr(FOREGROUND_BLUE | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
-		elif color == "reset":
-			set_text_attr(FOREGROUND_GREY | get_text_attr() & 0x0070)
-		elif color == "grey":
-			set_text_attr(FOREGROUND_GREY | get_text_attr() & 0x0070)
-	else :
-		if color == "green":
-			sys.stdout.write('\033[92m')
-		elif color == "red":
-			sys.stdout.write('\033[91m')
-		elif color == "blue":
-			sys.stdout.write('\033[94m')
-		elif color == "reset":
-			sys.stdout.write('\033[0m')
+    """Set the color. Works on Win32 and normal terminals."""
+    if sys.platform == "win32":
+        if color == "green":
+            set_text_attr(FOREGROUND_GREEN | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
+        elif color == "yellow":
+            set_text_attr(FOREGROUND_YELLOW | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
+        elif color == "red":
+            set_text_attr(FOREGROUND_RED | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
+        elif color == "blue":
+            set_text_attr(FOREGROUND_BLUE | get_text_attr() & 0x0070 | FOREGROUND_INTENSITY)
+        elif color == "reset":
+            set_text_attr(FOREGROUND_GREY | get_text_attr() & 0x0070)
+        elif color == "grey":
+            set_text_attr(FOREGROUND_GREY | get_text_attr() & 0x0070)
+    else :
+        if color == "green":
+            sys.stdout.write('\033[92m')
+        elif color == "red":
+            sys.stdout.write('\033[91m')
+        elif color == "blue":
+            sys.stdout.write('\033[94m')
+        elif color == "reset":
+            sys.stdout.write('\033[0m')
 
 def print_error(msg):
-	color("red")
-	print ("ERROR: " + msg)
-	color("reset")
+    color("red")
+    print ("ERROR: " + msg)
+    color("reset")
 
 def print_green(msg):
-	color("green")
-	print(msg)
-	color("reset")
+    color("green")
+    print(msg)
+    color("reset")
 
 def print_blue(msg):
-	color("blue")
-	print(msg)
-	color("reset")
+    color("blue")
+    print(msg)
+    color("reset")
 
 def print_yellow(msg):
-	color("yellow")
-	print(msg)
-	color("reset")
+    color("yellow")
+    print(msg)
+    color("reset")
 
-	
+    
 def copy_important_files(source_dir,destination_dir):
-	
-	originalDir = os.getcwd()
-	importantFiles = ["mod.cpp",
-	"README.md",
-	"AUTHORS.txt",
-	"LICENSE",
-	"logo_ace3_ca.paa"
-	]
-	
-	print_yellow ("source_dir: " + source_dir)
-	print_yellow("destination_dir: " + destination_dir)
-	
-	#copy importantFiles
-	try:
-		print_blue("\nSearching for important files in " + source_dir)
-		for file in importantFiles:
-			print_green("Copying file => " + os.path.join(source_dir,file))
-			shutil.copyfile(os.path.join(source_dir,file),os.path.join(destination_dir,file))
-	except:
-		print_error("COPYING IMPORTANT FILES.")
-		raise
-	
-	#copy all extension dlls
-	try:
-		os.chdir(os.path.join(source_dir))
-		print_blue("\nSearching for DLLs in " + os.getcwd())
-		filenames = glob.glob("*.dll")
-		
-		if not filenames:
-			print ("Empty SET")
-			
-		for dll in filenames:
-			print_green("Copying dll => " + os.path.join(source_dir,dll))
-			if os.path.isfile(dll):
-				shutil.copyfile(os.path.join(source_dir,dll),os.path.join(destination_dir,dll))
-	except:
-		print_error("COPYING DLL FILES.")
-		raise
-	finally:
-		os.chdir(originalDir)
+    
+    originalDir = os.getcwd()
+    importantFiles = ["mod.cpp",
+    "README.md",
+    "AUTHORS.txt",
+    "LICENSE",
+    "logo_ace3_ca.paa"
+    ]
+    
+    print_yellow ("source_dir: " + source_dir)
+    print_yellow("destination_dir: " + destination_dir)
+    
+    #copy importantFiles
+    try:
+        print_blue("\nSearching for important files in " + source_dir)
+        for file in importantFiles:
+            print_green("Copying file => " + os.path.join(source_dir,file))
+            shutil.copyfile(os.path.join(source_dir,file),os.path.join(destination_dir,file))
+    except:
+        print_error("COPYING IMPORTANT FILES.")
+        raise
+    
+    #copy all extension dlls
+    try:
+        os.chdir(os.path.join(source_dir))
+        print_blue("\nSearching for DLLs in " + os.getcwd())
+        filenames = glob.glob("*.dll")
+        
+        if not filenames:
+            print ("Empty SET")
+            
+        for dll in filenames:
+            print_green("Copying dll => " + os.path.join(source_dir,dll))
+            if os.path.isfile(dll):
+                shutil.copyfile(os.path.join(source_dir,dll),os.path.join(destination_dir,dll))
+    except:
+        print_error("COPYING DLL FILES.")
+        raise
+    finally:
+        os.chdir(originalDir)
 
 def copy_optionals_for_building(mod,pbos):
-	src_directories = os.listdir(optionals_root)
-	current_dir = os.getcwd()
-	
-	print("")
-	try:
-		
-		#special server.pbo processing
-		files = glob.glob(os.path.join(release_dir, "@ace","optionals","*.pbo"))
-		for file in files:
-			file_name = os.path.basename(file)
-			print ("Adding the following file: " + file_name)
-			pbos.append(file_name)
-			pbo_path = os.path.join(release_dir, "@ace","optionals",file_name)
-			if (os.path.isfile(pbo_path)):
-				print("Moving " + pbo_path + " for processing.")
-				shutil.move(pbo_path, os.path.join(release_dir,"@ace","addons",file_name))
+    src_directories = os.listdir(optionals_root)
+    current_dir = os.getcwd()
+    
+    print("")
+    try:
+        
+        #special server.pbo processing
+        files = glob.glob(os.path.join(release_dir, "@ace","optionals","*.pbo"))
+        for file in files:
+            file_name = os.path.basename(file)
+            print ("Adding the following file: " + file_name)
+            pbos.append(file_name)
+            pbo_path = os.path.join(release_dir, "@ace","optionals",file_name)
+            if (os.path.isfile(pbo_path)):
+                print("Moving " + pbo_path + " for processing.")
+                shutil.move(pbo_path, os.path.join(release_dir,"@ace","addons",file_name))
 
-	except:
-		print_error("Error in moving")
-		raise
-	finally:
-		os.chdir(current_dir)
+    except:
+        print_error("Error in moving")
+        raise
+    finally:
+        os.chdir(current_dir)
 
-	print("")
-	try:
-		for dir_name in src_directories:
-			mod.append(dir_name)
-			if (dir_name == "userconfig"):
-				destination = os.path.join(work_drive,dir_name)
-			else:
-				destination = os.path.join(module_root,dir_name)
+    print("")
+    try:
+        for dir_name in src_directories:
+            mod.append(dir_name)
+            if (dir_name == "userconfig"):
+                destination = os.path.join(work_drive,dir_name)
+            else:
+                destination = os.path.join(module_root,dir_name)
 
-			print("Temporarily copying " + os.path.join(optionals_root,dir_name) + " => " + destination + " for building.")
-			shutil.rmtree(destination, True)
-			shutil.copytree(os.path.join(optionals_root,dir_name), destination)
-	except:
-		print_error("Copy Optionals Failed")
-		raise
-	finally:
-		os.chdir(current_dir)
-		
+            print("Temporarily copying " + os.path.join(optionals_root,dir_name) + " => " + destination + " for building.")
+            shutil.rmtree(destination, True)
+            shutil.copytree(os.path.join(optionals_root,dir_name), destination)
+    except:
+        print_error("Copy Optionals Failed")
+        raise
+    finally:
+        os.chdir(current_dir)
+        
 def cleanup_optionals(mod,pbos):
-	print("")
-	try:			
-		for dir_name in mod:
-			if (dir_name == "userconfig"):
-				destination = os.path.join(work_drive,dir_name)
-			else:
-				destination = os.path.join(module_root,dir_name)
-			
-			print("Cleaning " + destination)
-			
-			try:
-				file_name = "ace_{}.pbo".format(dir_name)
-				src_file_path = os.path.join(release_dir, "@ace","addons",file_name)
-				dst_file_path = os.path.join(release_dir, "@ace","optionals",file_name)
-				if (os.path.isfile(src_file_path)):
-					#print("Preserving " + file_name)
-					os.renames(src_file_path,dst_file_path)
-			except FileExistsError:
-				print_error(file_name + " already exists")
-				continue
-			shutil.rmtree(destination)
-			
-	except:
-		print_error("Cleaning Optionals Failed")
-		raise
+    print("")
+    try:            
+        for dir_name in mod:
+            if (dir_name == "userconfig"):
+                destination = os.path.join(work_drive,dir_name)
+            else:
+                destination = os.path.join(module_root,dir_name)
+            
+            print("Cleaning " + destination)
+            
+            try:
+                file_name = "ace_{}.pbo".format(dir_name)
+                src_file_path = os.path.join(release_dir, "@ace","addons",file_name)
+                dst_file_path = os.path.join(release_dir, "@ace","optionals",file_name)
+                if (os.path.isfile(src_file_path)):
+                    #print("Preserving " + file_name)
+                    os.renames(src_file_path,dst_file_path)
+            except FileExistsError:
+                print_error(file_name + " already exists")
+                continue
+            shutil.rmtree(destination)
+            
+    except:
+        print_error("Cleaning Optionals Failed")
+        raise
 ###############################################################################
 
 def main(argv):
-	"""Build an Arma addon suite in a directory from rules in a make.cfg file."""
-	print_blue(("\nmake.py for Arma, modified for Advanced Combat Environment v" + __version__))
+    """Build an Arma addon suite in a directory from rules in a make.cfg file."""
+    print_blue(("\nmake.py for Arma, modified for Advanced Combat Environment v" + __version__))
 
-	if sys.platform != "win32":
-		print_error("Non-Windows platform (Cygwin?). Please re-run from cmd.")
-		sys.exit(1)
+    if sys.platform != "win32":
+        print_error("Non-Windows platform (Cygwin?). Please re-run from cmd.")
+        sys.exit(1)
 
-	reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
-	try:
-		k = winreg.OpenKey(reg, r"Software\bohemia interactive\arma 3 tools")
-		arma3tools_path = winreg.QueryValueEx(k, "path")[0]
-		winreg.CloseKey(k)
-	except:
-		raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
+    reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
+    try:
+        k = winreg.OpenKey(reg, r"Software\bohemia interactive\arma 3 tools")
+        arma3tools_path = winreg.QueryValueEx(k, "path")[0]
+        winreg.CloseKey(k)
+    except:
+        raise Exception("BadTools","Arma 3 Tools are not installed correctly or the P: drive needs to be created.")
 
-	# Default behaviors
-	test = False # Copy to Arma 3 directory?
-	arg_modules = False # Only build modules on command line?
-	make_release = False # Make zip file from the release?
-	release_version = 0 # Version of release
-	use_pboproject = True # Default to pboProject build tool
-	make_target = "DEFAULT" # Which section in make.cfg to use for the build
-	new_key = False # Make a new key and use it to sign?
-	quiet = False # Suppress output from build tool?
+    # Default behaviors
+    test = False # Copy to Arma 3 directory?
+    arg_modules = False # Only build modules on command line?
+    make_release = False # Make zip file from the release?
+    release_version = 0 # Version of release
+    use_pboproject = True # Default to pboProject build tool
+    make_target = "DEFAULT" # Which section in make.cfg to use for the build
+    new_key = False # Make a new key and use it to sign?
+    quiet = False # Suppress output from build tool?
 
-	# Parse arguments
-	if "help" in argv or "-h" in argv or "--help" in argv:
-		print ("""
+    # Parse arguments
+    if "help" in argv or "-h" in argv or "--help" in argv:
+        print ("""
 make.py [help] [test] [force] [key <name>] [target <name>] [release <version>]
         [module name] [module name] [...]
 
@@ -463,539 +465,539 @@ If a file called $NOBIN$ is found in the module directory, that module will not
 
 See the make.cfg file for additional build options.
 """)
-		sys.exit(0)
-
-	if "force" in argv:
-		argv.remove("force")
-		force_build = True
-	else:
-		force_build = False
-
-	if "test" in argv:
-		test = True
-		argv.remove("test")
-
-	if "release" in argv:
-		make_release = True
-		release_version = argv[argv.index("release") + 1]
-		argv.remove(release_version)
-		argv.remove("release")
-
-	if "target" in argv:
-		make_target = argv[argv.index("target") + 1]
-		argv.remove("target")
-		argv.remove(make_target)
-		force_build = True
-
-	if "key" in argv:
-		new_key = True
-		key_name = argv[argv.index("key") + 1]
-		argv.remove("key")
-		argv.remove(key_name)
-
-	if "quiet" in argv:
-		quiet = True
-		argv.remove("quiet")
-
-	if "checkexternal" in argv:
-		argv.remove("checkexternal")
-		check_external = True
-	else:
-		check_external = False
-
-	# Get the directory the make script is in.
-	make_root = os.path.dirname(os.path.realpath(__file__))
-	make_root_parent = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
-	os.chdir(make_root)
-
-	# Get latest commit ID
-	try:
-		gitpath = os.path.join(os.path.dirname(make_root), ".git")
-		assert os.path.exists(gitpath)
-
-		commit_id = subprocess.check_output(["git", "rev-parse", "HEAD"])
-		commit_id = str(commit_id, "utf-8")[:8]
-	except:
-		print_error("FAILED TO DETERMINE COMMIT ID.")
-		commit_id = "NOGIT"
-
-	cfg = configparser.ConfigParser();
-	try:
-		global work_drive
-		global module_root
-		global release_dir
-		global module_root_parent
-		global optionals_root
-		
-		cfg.read(os.path.join(make_root, "make.cfg"))
-
-		# Project name (with @ symbol)
-		project = cfg.get(make_target, "project", fallback="@"+os.path.basename(os.getcwd()))
-
-		# Private key path
-		key = cfg.get(make_target, "key", fallback=None)
-
-		# Project prefix (folder path)
-		prefix = cfg.get(make_target, "prefix", fallback="")
-
-		# Should we autodetect modules on a complete build?
-		module_autodetect = cfg.getboolean(make_target, "module_autodetect", fallback=True)
-
-		# Manual list of modules to build for a complete build
-		modules = cfg.get(make_target, "modules", fallback=None)
-		# Parse it out
-		if modules:
-			modules = [x.strip() for x in modules.split(',')]
-		else:
-			modules = []
-
-		# List of directories to ignore when detecting
-		ignore = [x.strip() for x in cfg.get(make_target, "ignore",  fallback="release").split(',')]
-
-		# BI Tools work drive on Windows
-		work_drive = cfg.get(make_target, "work_drive",  fallback="P:\\")
-
-		# Which build tool should we use?
-		build_tool = cfg.get(make_target, "build_tool", fallback="addonbuilder").lower()
-
-		# Release/build directory, relative to script dir
-		release_dir = cfg.get(make_target, "release_dir", fallback="release")
-		
-		# Project PBO file prefix (files are renamed to prefix_name.pbo)
-		pbo_name_prefix = cfg.get(make_target, "pbo_name_prefix", fallback=None)
-
-		# Project module Root
-		module_root_parent = os.path.abspath(os.path.join(os.path.join(work_drive, prefix), os.pardir))
-		module_root = cfg.get(make_target, "module_root", fallback=os.path.join(make_root_parent, "addons"))
-		optionals_root = os.path.join(module_root_parent, "optionals")
-		print_green ("module_root: " + module_root)
-		
-		if (os.path.isdir(module_root)):
-			os.chdir(module_root)
-		else:
-			print_error ("Directory " + module_root + " does not exist.")
-			sys.exit()
-
-		if (os.path.isdir(optionals_root)):
-			print_green ("optionals_root: " + optionals_root)
-		else:
-			print_error ("Directory " + optionals_root + " does not exist.")
-			sys.exit()
-		
-		print_green ("release_dir: " + release_dir)
-		
-	except:
-		raise
-		print_error("Could not parse make.cfg.")
-		sys.exit(1)
-
-	# See if we have been given specific modules to build from command line.
-	if len(argv) > 1 and not make_release:
-		arg_modules = True
-		modules = argv[1:]
-
-	# Find the tools we need.
-	try:
-		tools = find_bi_tools(work_drive)
-		addonbuilder = tools[0]
-		dssignfile = tools[1]
-		dscreatekey = tools[2]
-		cfgconvert = tools[3]
-
-	except:
-		print_error("Arma 3 Tools are not installed correctly or the P: drive has not been created.")
-		sys.exit(1)
-
-	if build_tool == "pboproject":
-		try:
-			depbo_tools = find_depbo_tools("HKLM")
-			if depbo_tools == -1:
-				depbo_tools = find_depbo_tools("HKCU")
-			pboproject = depbo_tools[0]
-			rapifyTool = depbo_tools[1]
-			makepboTool = depbo_tools[2]
-		except:
-			raise
-			print_error("Could not find dePBO tools. Download the needed tools from: https://dev.withsix.com/projects/mikero-pbodll/files")
-			sys.exit(1)
-
-	# Try to open and deserialize build cache file.
-	try:
-		cache = {}
-		with open(os.path.join(make_root, "make.cache"), 'r') as f:
-			cache_raw = f.read()
-
-		cache = json.loads(cache_raw)
-
-	except:
-		print ("No cache found.")
-		cache = {}
-
-	#Temporarily copy optionals_root for building. They will be removed later. 
-	optionals_modules = []
-	optional_files = []
-	copy_optionals_for_building(optionals_modules,optional_files)
-	
-	# Get list of subdirs in make root.
-	dirs = next(os.walk(module_root))[1]
-	
-	# Autodetect what directories to build.
-	if module_autodetect and not arg_modules:
-		modules = []
-		for path in dirs:
-			# Any dir that has a config.cpp in its root is an addon to build.
-			config_path = os.path.join(path, 'config.cpp')
-			if os.path.isfile(config_path) and not path in ignore:
-				modules.append(path)
-
-	# Make the key specified from command line if necessary.
-	if new_key:
-		if not os.path.isfile(os.path.join(module_root, key_name + ".biprivatekey")):
-			print_green("\nRequested key does not exist.")
-			ret = subprocess.call([dscreatekey, key_name]) # Created in make_root
-			if ret == 0:
-				print_blue("Created: " + os.path.join(module_root, key_name + ".biprivatekey"))
-			else:
-				print_error("Failed to create key!")
-
-			try:
-				print_blue("Copying public key to release directory.")
-
-				try:
-					os.makedirs(os.path.join(module_root, release_dir, "Keys"))
-				except:
-					pass
-
-				shutil.copyfile(os.path.join(module_root, key_name + ".bikey"), os.path.join(module_root, release_dir, "Keys", key_name + ".bikey"))
-
-			except:
-				raise
-				print_error("Could not copy key to release directory.")
-
-		else:
-			print_green("\nNOTE: Using key " + os.path.join(module_root, key_name + ".biprivatekey"))
-
-		key = os.path.join(module_root, key_name + ".biprivatekey")
-
-
-	# For each module, prep files and then build.
-	for module in modules:
-		print_green("\nMaking " + module + "-"*max(1, (60-len(module))))
-		missing = False
-		
-		# Cache check
-		if module in cache:
-			old_sha = cache[module]
-		else:
-			old_sha = ""
-
-		#We always build ACE_common so we can properly show the correct version stamp in the RPT file.
-		if module == "common":
-			old_sha = ""
-
-		# Hash the module
-		new_sha = get_directory_hash(os.path.join(module_root, module))
-		
-		# Is the pbo file missing?
-		missing = not os.path.isfile(os.path.join(release_dir, project, "addons", "ace_{}.pbo".format(module)))
-		if missing:
-			print("ace_{}.pbo".format(module) + " is missing. Building...")
-
-		# Check if it needs rebuilt
-		# print ("Hash:", new_sha)
-		if old_sha == new_sha and not missing:
-			if not force_build:
-				print("Module has not changed.")
-				# Skip everything else
-				continue
-
-		# Only do this if the project isn't stored directly on the work drive.
-		# Split the path at the drive name and see if they are on the same drive (usually P:)
-		if os.path.splitdrive(module_root)[0] != os.path.splitdrive(work_drive)[0]:
-			try:
-				# Remove old work drive version (ignore errors)
-				shutil.rmtree(os.path.join(work_drive, prefix, module), True)
-
-				# Copy module to the work drive
-				shutil.copytree(module, os.path.join(work_drive, prefix, module))
-
-			except:
-				raise
-				print_error("ERROR: Could not copy module to work drive. Does the module exist?")
-				input("Press Enter to continue...")
-				print("Resuming build...")
-				continue
-		#else:
-			#print("WARNING: Module is stored on work drive (" + work_drive + ").")
-
-		try:
-			# Remove the old pbo, key, and log
-			old = os.path.join(module_root, release_dir, project, "Addons", module) + "*"
-			files = glob.glob(old)
-			for f in files:
-				os.remove(f)
-
-			if pbo_name_prefix:
-				old = os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix+module) + "*"
-				files = glob.glob(old)
-				for f in files:
-					os.remove(f)
-		except:
-			raise
-			print_error("ERROR: Could not copy module to work drive. Does the module exist?")
-			input("Press Enter to continue...")
-			print("Resuming build...")
-			continue
-
-		# Build the module into a pbo
-		print_blue("Building: " + os.path.join(work_drive, prefix, module))
-		print_blue("Destination: " + os.path.join(module_root, release_dir, project, "Addons"))
-
-		# Make destination folder (if needed)
-		try:
-			os.makedirs(os.path.join(module_root, release_dir, project, "Addons"))
-		except:
-			pass
-
-		# Run build tool
-		build_successful = False
-		if build_tool == "pboproject":
-			try:
-				#PABST: Convert config (run the macro'd config.cpp through CfgConvert twice to produce a de-macro'd cpp that pboProject can read without fucking up:
-				shutil.copyfile(os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.backup"))
-
-				os.chdir("P:\\")
-
-				cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-bin", "-dst", os.path.join(work_drive, prefix, module, "config.bin"), os.path.join(work_drive, prefix, module, "config.cpp")]
-				ret = subprocess.call(cmd)
-				if ret != 0:
-					print_error("CfgConvert -bin return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.")
-					os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
-					shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
-
-				cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-txt", "-dst", os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.bin")]
-				ret = subprocess.call(cmd)
-				if ret != 0:
-					print_error("CfgConvert -txt return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.")
-					os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
-					shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
-					
-
-				# Include build number
-				try:
-					configpath = os.path.join(work_drive, prefix, module, "config.cpp")
-					f = open(configpath, "r")
-					configtext = f.read()
-					f.close()
-
-					if configtext:
-						patchestext = re.search(r"class CfgPatches\n\{(.*?)\n\}", configtext, re.DOTALL).group(1)
-						patchestext = re.sub(r'version(.*?)="(.*?)"', r'version\1="\2-{}"'.format(commit_id), patchestext)
-						configtext = re.sub(r"class CfgPatches\n\{(.*?)\n\}", "class CfgPatches\n{"+patchestext+"\n}", configtext, flags=re.DOTALL)
-						f = open(configpath, "w")
-						f.write(configtext)
-						f.close()
-					else:
-						os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
-						os.rename(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))	
-				except:
-					raise
-					print_error("Failed to include build number")
-					continue
-
-				if os.path.isfile(os.path.join(work_drive, prefix, module, "$NOBIN$")):
-					print_green("$NOBIN$ Found. Proceeding with non-binarizing!")
-					cmd = [makepboTool, "-P","-A","-L","-N","-G", os.path.join(work_drive, prefix, module),os.path.join(module_root, release_dir, project,"Addons")]
-
-				else:
-					if check_external:
-						cmd = [pboproject, "-P", os.path.join(work_drive, prefix, module), "+Engine=Arma3", "-S","+Noisy", "+X", "+Clean", "+Mod="+os.path.join(module_root, release_dir, project), "-Key"]
-					else:
-						cmd = [pboproject, "-P", os.path.join(work_drive, prefix, module), "+Engine=Arma3", "-S","+Noisy", "-X", "+Clean", "+Mod="+os.path.join(module_root, release_dir, project), "-Key"]
-
-				color("grey")
-				if quiet:
-					devnull = open(os.devnull, 'w')
-					ret = subprocess.call(cmd, stdout=devnull)
-					devnull.close()
-				else:
-					ret = subprocess.call(cmd)
-				color("reset")
-
-				if ret == 0:
-					print_green("pboProject return code == " + str(ret))
-					# Prettyprefix rename the PBO if requested.
-					if pbo_name_prefix:
-						try:
-							os.rename(os.path.join(module_root, release_dir, project, "Addons", module+".pbo"), os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix+module+".pbo"))
-						except:
-							raise
-							print_error("Could not rename built PBO with prefix.")
-					# Sign result
-					if key:
-						print("Signing with " + key + ".")
-						if pbo_name_prefix:
-							ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix + module + ".pbo")])
-						else:
-							ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "Addons", module + ".pbo")])
-
-						if ret == 0:
-							build_successful = True
-					else:
-						build_successful = True
-
-				if not build_successful:
-					print_error("pboProject return code == " + str(ret))
-					print_error("Module not successfully built/signed.")
-					print ("Resuming build...")
-					continue
-
-				#PABST: cleanup config BS (you could comment this out to see the "de-macroed" cpp
-				#print_green("\Pabst (restoring): " + os.path.join(work_drive, prefix, module, "config.cpp"))
-				os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
-				os.remove(os.path.join(work_drive, prefix, module, "config.bin"))
-				os.rename(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
-
-				# Back to the root
-				os.chdir(module_root)
-
-			except:
-				raise
-				print_error("Could not run Addon Builder.")
-				input("Press Enter to continue...")
-				print ("Resuming build...")
-				continue
-
-		elif build_tool== "addonbuilder":
-			# Detect $NOBIN$ and do not binarize if found.
-			if os.path.isfile(os.path.join(work_drive, prefix, module, "$NOBIN$")):
-				do_binarize = False
-				print("$NOBIN$ file found in module, packing only.")
-			else:
-				do_binarize = True
-			try:
-				# Call AddonBuilder
-				os.chdir("P:\\")
-
-				cmd = [addonbuilder, os.path.join(work_drive, prefix, module), os.path.join(make_root, release_dir, project, "Addons"), "-clear", "-project="+work_drive]
-				if not do_binarize:
-					cmd.append("-packonly")
-
-				if quiet:
-					previousDirectory = os.getcwd()
-					os.chdir(arma3tools_path)
-					devnull = open(os.devnull, 'w')
-					ret = subprocess.call(cmd, stdout=devnull)
-					devnull.close()
-					os.chdir(previousDirectory)
-				else:
-					previousDirectory = os.getcwd()
-					os.chdir(arma3tools_path)
-					print_error("Current directory - " + os.getcwd())
-					ret = subprocess.call(cmd)
-					os.chdir(previousDirectory)
-					print_error("Current directory - " + os.getcwd())
-				color("reset")
-				print_green("completed")
-				# Prettyprefix rename the PBO if requested.
-				if pbo_name_prefix:
-					try:
-						os.rename(os.path.join(make_root, release_dir, project, "Addons", module+".pbo"), os.path.join(make_root, release_dir, project, "Addons", pbo_name_prefix+module+".pbo"))
-					except:
-						raise
-						print_error("Could not rename built PBO with prefix.")
-
-				if ret == 0:
-					# Sign result
-					if key:
-						print("Signing with " + key + ".")
-						if pbo_name_prefix:
-							ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "Addons", pbo_name_prefix + module + ".pbo")])
-						else:
-							ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "Addons", module + ".pbo")])
-
-						if ret == 0:
-							build_successful = True
-					else:
-						build_successful = True
-
-				if not build_successful:
-					print_error("Module not successfully built.")
-
-				# Back to the root
-				os.chdir(make_root)
-
-			except:
-				raise
-				print_error("Could not run Addon Builder.")
-				input("Press Enter to continue...")
-				print ("Resuming build...")
-				continue
-
-		else:
-			print_error("Unknown build_tool " + build_tool + "!")
-
-		# Update the hash for a successfully built module
-		if build_successful:
-			cache[module] = new_sha
-
-	# Done building all modules!
-
-	# Write out the cache state
-	cache_out = json.dumps(cache)
-	with open(os.path.join(make_root, "make.cache"), 'w') as f:
-		f.write(cache_out)
-
-	# Delete the pboproject temp files if building a release.
-	if make_release and build_tool == "pboproject":
-		try:
-			shutil.rmtree(os.path.join(module_root, release_dir, project, "temp"), True)
-		except:
-			print_error("ERROR: Could not delete pboProject temp files.")
-
-	print_green("\nDone.")
-
-	# Make release
-	if make_release:
-		print_blue("\nMaking release: " + project + "-" + release_version + ".zip")
-
-		try:
-			# Delete all log files
-			for root, dirs, files in os.walk(os.path.join(module_root, release_dir, project, "Addons")):
-				for currentFile in files:
-					if currentFile.lower().endswith("log"):
-						os.remove(os.path.join(root, currentFile))
-
-			# Create a zip with the contents of release/ in it
-			shutil.make_archive(project + "-" + release_version, "zip", os.path.join(module_root, release_dir))
-		except:
-			raise
-			print_error("Could not make release.")
-
-	# Copy to Arma 3 folder for testing
-	if test:
-		print_blue("\nCopying to Arma 3.")
-
-		if sys.platform == "win32":
-			reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
-			try:
-				k = winreg.OpenKey(reg, r"SOFTWARE\Wow6432Node\Bohemia Interactive\Arma 3")
-				a3_path = winreg.EnumValue(k, 1)[1]
-				winreg.CloseKey(k)
-			except:
-				print_error("Could not find Arma 3's directory in the registry.")
-		else:
-			a3_path = cygwin_a3path
-
-		if os.path.exists(a3_path):
-			try:
-				shutil.rmtree(os.path.join(a3_path, project), True)
-				shutil.copytree(os.path.join(module_root, release_dir, project), os.path.join(a3_path, project))
-			except:
-				print_error("Could not copy files. Is Arma 3 running?")
-
-	copy_important_files(module_root_parent,os.path.join(release_dir, "@ace"))
-	cleanup_optionals(optionals_modules,optional_files)
+        sys.exit(0)
+
+    if "force" in argv:
+        argv.remove("force")
+        force_build = True
+    else:
+        force_build = False
+
+    if "test" in argv:
+        test = True
+        argv.remove("test")
+
+    if "release" in argv:
+        make_release = True
+        release_version = argv[argv.index("release") + 1]
+        argv.remove(release_version)
+        argv.remove("release")
+
+    if "target" in argv:
+        make_target = argv[argv.index("target") + 1]
+        argv.remove("target")
+        argv.remove(make_target)
+        force_build = True
+
+    if "key" in argv:
+        new_key = True
+        key_name = argv[argv.index("key") + 1]
+        argv.remove("key")
+        argv.remove(key_name)
+
+    if "quiet" in argv:
+        quiet = True
+        argv.remove("quiet")
+
+    if "checkexternal" in argv:
+        argv.remove("checkexternal")
+        check_external = True
+    else:
+        check_external = False
+
+    # Get the directory the make script is in.
+    make_root = os.path.dirname(os.path.realpath(__file__))
+    make_root_parent = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
+    os.chdir(make_root)
+
+    # Get latest commit ID
+    try:
+        gitpath = os.path.join(os.path.dirname(make_root), ".git")
+        assert os.path.exists(gitpath)
+
+        commit_id = subprocess.check_output(["git", "rev-parse", "HEAD"])
+        commit_id = str(commit_id, "utf-8")[:8]
+    except:
+        print_error("FAILED TO DETERMINE COMMIT ID.")
+        commit_id = "NOGIT"
+
+    cfg = configparser.ConfigParser();
+    try:
+        global work_drive
+        global module_root
+        global release_dir
+        global module_root_parent
+        global optionals_root
+        
+        cfg.read(os.path.join(make_root, "make.cfg"))
+
+        # Project name (with @ symbol)
+        project = cfg.get(make_target, "project", fallback="@"+os.path.basename(os.getcwd()))
+
+        # Private key path
+        key = cfg.get(make_target, "key", fallback=None)
+
+        # Project prefix (folder path)
+        prefix = cfg.get(make_target, "prefix", fallback="")
+
+        # Should we autodetect modules on a complete build?
+        module_autodetect = cfg.getboolean(make_target, "module_autodetect", fallback=True)
+
+        # Manual list of modules to build for a complete build
+        modules = cfg.get(make_target, "modules", fallback=None)
+        # Parse it out
+        if modules:
+            modules = [x.strip() for x in modules.split(',')]
+        else:
+            modules = []
+
+        # List of directories to ignore when detecting
+        ignore = [x.strip() for x in cfg.get(make_target, "ignore",  fallback="release").split(',')]
+
+        # BI Tools work drive on Windows
+        work_drive = cfg.get(make_target, "work_drive",  fallback="P:\\")
+
+        # Which build tool should we use?
+        build_tool = cfg.get(make_target, "build_tool", fallback="addonbuilder").lower()
+
+        # Release/build directory, relative to script dir
+        release_dir = cfg.get(make_target, "release_dir", fallback="release")
+        
+        # Project PBO file prefix (files are renamed to prefix_name.pbo)
+        pbo_name_prefix = cfg.get(make_target, "pbo_name_prefix", fallback=None)
+
+        # Project module Root
+        module_root_parent = os.path.abspath(os.path.join(os.path.join(work_drive, prefix), os.pardir))
+        module_root = cfg.get(make_target, "module_root", fallback=os.path.join(make_root_parent, "addons"))
+        optionals_root = os.path.join(module_root_parent, "optionals")
+        print_green ("module_root: " + module_root)
+        
+        if (os.path.isdir(module_root)):
+            os.chdir(module_root)
+        else:
+            print_error ("Directory " + module_root + " does not exist.")
+            sys.exit()
+
+        if (os.path.isdir(optionals_root)):
+            print_green ("optionals_root: " + optionals_root)
+        else:
+            print_error ("Directory " + optionals_root + " does not exist.")
+            sys.exit()
+        
+        print_green ("release_dir: " + release_dir)
+        
+    except:
+        raise
+        print_error("Could not parse make.cfg.")
+        sys.exit(1)
+
+    # See if we have been given specific modules to build from command line.
+    if len(argv) > 1 and not make_release:
+        arg_modules = True
+        modules = argv[1:]
+
+    # Find the tools we need.
+    try:
+        tools = find_bi_tools(work_drive)
+        addonbuilder = tools[0]
+        dssignfile = tools[1]
+        dscreatekey = tools[2]
+        cfgconvert = tools[3]
+
+    except:
+        print_error("Arma 3 Tools are not installed correctly or the P: drive has not been created.")
+        sys.exit(1)
+
+    if build_tool == "pboproject":
+        try:
+            depbo_tools = find_depbo_tools("HKLM")
+            if depbo_tools == -1:
+                depbo_tools = find_depbo_tools("HKCU")
+            pboproject = depbo_tools[0]
+            rapifyTool = depbo_tools[1]
+            makepboTool = depbo_tools[2]
+        except:
+            raise
+            print_error("Could not find dePBO tools. Download the needed tools from: https://dev.withsix.com/projects/mikero-pbodll/files")
+            sys.exit(1)
+
+    # Try to open and deserialize build cache file.
+    try:
+        cache = {}
+        with open(os.path.join(make_root, "make.cache"), 'r') as f:
+            cache_raw = f.read()
+
+        cache = json.loads(cache_raw)
+
+    except:
+        print ("No cache found.")
+        cache = {}
+
+    #Temporarily copy optionals_root for building. They will be removed later. 
+    optionals_modules = []
+    optional_files = []
+    copy_optionals_for_building(optionals_modules,optional_files)
+    
+    # Get list of subdirs in make root.
+    dirs = next(os.walk(module_root))[1]
+    
+    # Autodetect what directories to build.
+    if module_autodetect and not arg_modules:
+        modules = []
+        for path in dirs:
+            # Any dir that has a config.cpp in its root is an addon to build.
+            config_path = os.path.join(path, 'config.cpp')
+            if os.path.isfile(config_path) and not path in ignore:
+                modules.append(path)
+
+    # Make the key specified from command line if necessary.
+    if new_key:
+        if not os.path.isfile(os.path.join(module_root, key_name + ".biprivatekey")):
+            print_green("\nRequested key does not exist.")
+            ret = subprocess.call([dscreatekey, key_name]) # Created in make_root
+            if ret == 0:
+                print_blue("Created: " + os.path.join(module_root, key_name + ".biprivatekey"))
+            else:
+                print_error("Failed to create key!")
+
+            try:
+                print_blue("Copying public key to release directory.")
+
+                try:
+                    os.makedirs(os.path.join(module_root, release_dir, "Keys"))
+                except:
+                    pass
+
+                shutil.copyfile(os.path.join(module_root, key_name + ".bikey"), os.path.join(module_root, release_dir, "Keys", key_name + ".bikey"))
+
+            except:
+                raise
+                print_error("Could not copy key to release directory.")
+
+        else:
+            print_green("\nNOTE: Using key " + os.path.join(module_root, key_name + ".biprivatekey"))
+
+        key = os.path.join(module_root, key_name + ".biprivatekey")
+
+
+    # For each module, prep files and then build.
+    for module in modules:
+        print_green("\nMaking " + module + "-"*max(1, (60-len(module))))
+        missing = False
+        
+        # Cache check
+        if module in cache:
+            old_sha = cache[module]
+        else:
+            old_sha = ""
+
+        #We always build ACE_common so we can properly show the correct version stamp in the RPT file.
+        if module == "common":
+            old_sha = ""
+
+        # Hash the module
+        new_sha = get_directory_hash(os.path.join(module_root, module))
+        
+        # Is the pbo file missing?
+        missing = not os.path.isfile(os.path.join(release_dir, project, "addons", "ace_{}.pbo".format(module)))
+        if missing:
+            print("ace_{}.pbo".format(module) + " is missing. Building...")
+
+        # Check if it needs rebuilt
+        # print ("Hash:", new_sha)
+        if old_sha == new_sha and not missing:
+            if not force_build:
+                print("Module has not changed.")
+                # Skip everything else
+                continue
+
+        # Only do this if the project isn't stored directly on the work drive.
+        # Split the path at the drive name and see if they are on the same drive (usually P:)
+        if os.path.splitdrive(module_root)[0] != os.path.splitdrive(work_drive)[0]:
+            try:
+                # Remove old work drive version (ignore errors)
+                shutil.rmtree(os.path.join(work_drive, prefix, module), True)
+
+                # Copy module to the work drive
+                shutil.copytree(module, os.path.join(work_drive, prefix, module))
+
+            except:
+                raise
+                print_error("ERROR: Could not copy module to work drive. Does the module exist?")
+                input("Press Enter to continue...")
+                print("Resuming build...")
+                continue
+        #else:
+            #print("WARNING: Module is stored on work drive (" + work_drive + ").")
+
+        try:
+            # Remove the old pbo, key, and log
+            old = os.path.join(module_root, release_dir, project, "Addons", module) + "*"
+            files = glob.glob(old)
+            for f in files:
+                os.remove(f)
+
+            if pbo_name_prefix:
+                old = os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix+module) + "*"
+                files = glob.glob(old)
+                for f in files:
+                    os.remove(f)
+        except:
+            raise
+            print_error("ERROR: Could not copy module to work drive. Does the module exist?")
+            input("Press Enter to continue...")
+            print("Resuming build...")
+            continue
+
+        # Build the module into a pbo
+        print_blue("Building: " + os.path.join(work_drive, prefix, module))
+        print_blue("Destination: " + os.path.join(module_root, release_dir, project, "Addons"))
+
+        # Make destination folder (if needed)
+        try:
+            os.makedirs(os.path.join(module_root, release_dir, project, "Addons"))
+        except:
+            pass
+
+        # Run build tool
+        build_successful = False
+        if build_tool == "pboproject":
+            try:
+                #PABST: Convert config (run the macro'd config.cpp through CfgConvert twice to produce a de-macro'd cpp that pboProject can read without fucking up:
+                shutil.copyfile(os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.backup"))
+
+                os.chdir("P:\\")
+
+                cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-bin", "-dst", os.path.join(work_drive, prefix, module, "config.bin"), os.path.join(work_drive, prefix, module, "config.cpp")]
+                ret = subprocess.call(cmd)
+                if ret != 0:
+                    print_error("CfgConvert -bin return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.")
+                    os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
+                    shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
+
+                cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-txt", "-dst", os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.bin")]
+                ret = subprocess.call(cmd)
+                if ret != 0:
+                    print_error("CfgConvert -txt return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.")
+                    os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
+                    shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
+                    
+
+                # Include build number
+                try:
+                    configpath = os.path.join(work_drive, prefix, module, "config.cpp")
+                    f = open(configpath, "r")
+                    configtext = f.read()
+                    f.close()
+
+                    if configtext:
+                        patchestext = re.search(r"class CfgPatches\n\{(.*?)\n\}", configtext, re.DOTALL).group(1)
+                        patchestext = re.sub(r'version(.*?)="(.*?)"', r'version\1="\2-{}"'.format(commit_id), patchestext)
+                        configtext = re.sub(r"class CfgPatches\n\{(.*?)\n\}", "class CfgPatches\n{"+patchestext+"\n}", configtext, flags=re.DOTALL)
+                        f = open(configpath, "w")
+                        f.write(configtext)
+                        f.close()
+                    else:
+                        os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
+                        os.rename(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))    
+                except:
+                    raise
+                    print_error("Failed to include build number")
+                    continue
+
+                if os.path.isfile(os.path.join(work_drive, prefix, module, "$NOBIN$")):
+                    print_green("$NOBIN$ Found. Proceeding with non-binarizing!")
+                    cmd = [makepboTool, "-P","-A","-L","-N","-G", os.path.join(work_drive, prefix, module),os.path.join(module_root, release_dir, project,"Addons")]
+
+                else:
+                    if check_external:
+                        cmd = [pboproject, "-P", os.path.join(work_drive, prefix, module), "+Engine=Arma3", "-S","+Noisy", "+X", "+Clean", "+Mod="+os.path.join(module_root, release_dir, project), "-Key"]
+                    else:
+                        cmd = [pboproject, "-P", os.path.join(work_drive, prefix, module), "+Engine=Arma3", "-S","+Noisy", "-X", "+Clean", "+Mod="+os.path.join(module_root, release_dir, project), "-Key"]
+
+                color("grey")
+                if quiet:
+                    devnull = open(os.devnull, 'w')
+                    ret = subprocess.call(cmd, stdout=devnull)
+                    devnull.close()
+                else:
+                    ret = subprocess.call(cmd)
+                color("reset")
+
+                if ret == 0:
+                    print_green("pboProject return code == " + str(ret))
+                    # Prettyprefix rename the PBO if requested.
+                    if pbo_name_prefix:
+                        try:
+                            os.rename(os.path.join(module_root, release_dir, project, "Addons", module+".pbo"), os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix+module+".pbo"))
+                        except:
+                            raise
+                            print_error("Could not rename built PBO with prefix.")
+                    # Sign result
+                    if key:
+                        print("Signing with " + key + ".")
+                        if pbo_name_prefix:
+                            ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "Addons", pbo_name_prefix + module + ".pbo")])
+                        else:
+                            ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "Addons", module + ".pbo")])
+
+                        if ret == 0:
+                            build_successful = True
+                    else:
+                        build_successful = True
+
+                if not build_successful:
+                    print_error("pboProject return code == " + str(ret))
+                    print_error("Module not successfully built/signed.")
+                    print ("Resuming build...")
+                    continue
+
+                #PABST: cleanup config BS (you could comment this out to see the "de-macroed" cpp
+                #print_green("\Pabst (restoring): " + os.path.join(work_drive, prefix, module, "config.cpp"))
+                os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
+                os.remove(os.path.join(work_drive, prefix, module, "config.bin"))
+                os.rename(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
+
+                # Back to the root
+                os.chdir(module_root)
+
+            except:
+                raise
+                print_error("Could not run Addon Builder.")
+                input("Press Enter to continue...")
+                print ("Resuming build...")
+                continue
+
+        elif build_tool== "addonbuilder":
+            # Detect $NOBIN$ and do not binarize if found.
+            if os.path.isfile(os.path.join(work_drive, prefix, module, "$NOBIN$")):
+                do_binarize = False
+                print("$NOBIN$ file found in module, packing only.")
+            else:
+                do_binarize = True
+            try:
+                # Call AddonBuilder
+                os.chdir("P:\\")
+
+                cmd = [addonbuilder, os.path.join(work_drive, prefix, module), os.path.join(make_root, release_dir, project, "Addons"), "-clear", "-project="+work_drive]
+                if not do_binarize:
+                    cmd.append("-packonly")
+
+                if quiet:
+                    previousDirectory = os.getcwd()
+                    os.chdir(arma3tools_path)
+                    devnull = open(os.devnull, 'w')
+                    ret = subprocess.call(cmd, stdout=devnull)
+                    devnull.close()
+                    os.chdir(previousDirectory)
+                else:
+                    previousDirectory = os.getcwd()
+                    os.chdir(arma3tools_path)
+                    print_error("Current directory - " + os.getcwd())
+                    ret = subprocess.call(cmd)
+                    os.chdir(previousDirectory)
+                    print_error("Current directory - " + os.getcwd())
+                color("reset")
+                print_green("completed")
+                # Prettyprefix rename the PBO if requested.
+                if pbo_name_prefix:
+                    try:
+                        os.rename(os.path.join(make_root, release_dir, project, "Addons", module+".pbo"), os.path.join(make_root, release_dir, project, "Addons", pbo_name_prefix+module+".pbo"))
+                    except:
+                        raise
+                        print_error("Could not rename built PBO with prefix.")
+
+                if ret == 0:
+                    # Sign result
+                    if key:
+                        print("Signing with " + key + ".")
+                        if pbo_name_prefix:
+                            ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "Addons", pbo_name_prefix + module + ".pbo")])
+                        else:
+                            ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "Addons", module + ".pbo")])
+
+                        if ret == 0:
+                            build_successful = True
+                    else:
+                        build_successful = True
+
+                if not build_successful:
+                    print_error("Module not successfully built.")
+
+                # Back to the root
+                os.chdir(make_root)
+
+            except:
+                raise
+                print_error("Could not run Addon Builder.")
+                input("Press Enter to continue...")
+                print ("Resuming build...")
+                continue
+
+        else:
+            print_error("Unknown build_tool " + build_tool + "!")
+
+        # Update the hash for a successfully built module
+        if build_successful:
+            cache[module] = new_sha
+
+    # Done building all modules!
+
+    # Write out the cache state
+    cache_out = json.dumps(cache)
+    with open(os.path.join(make_root, "make.cache"), 'w') as f:
+        f.write(cache_out)
+
+    # Delete the pboproject temp files if building a release.
+    if make_release and build_tool == "pboproject":
+        try:
+            shutil.rmtree(os.path.join(module_root, release_dir, project, "temp"), True)
+        except:
+            print_error("ERROR: Could not delete pboProject temp files.")
+
+    print_green("\nDone.")
+
+    # Make release
+    if make_release:
+        print_blue("\nMaking release: " + project + "-" + release_version + ".zip")
+
+        try:
+            # Delete all log files
+            for root, dirs, files in os.walk(os.path.join(module_root, release_dir, project, "Addons")):
+                for currentFile in files:
+                    if currentFile.lower().endswith("log"):
+                        os.remove(os.path.join(root, currentFile))
+
+            # Create a zip with the contents of release/ in it
+            shutil.make_archive(project + "-" + release_version, "zip", os.path.join(module_root, release_dir))
+        except:
+            raise
+            print_error("Could not make release.")
+
+    # Copy to Arma 3 folder for testing
+    if test:
+        print_blue("\nCopying to Arma 3.")
+
+        if sys.platform == "win32":
+            reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+            try:
+                k = winreg.OpenKey(reg, r"SOFTWARE\Wow6432Node\Bohemia Interactive\Arma 3")
+                a3_path = winreg.EnumValue(k, 1)[1]
+                winreg.CloseKey(k)
+            except:
+                print_error("Could not find Arma 3's directory in the registry.")
+        else:
+            a3_path = cygwin_a3path
+
+        if os.path.exists(a3_path):
+            try:
+                shutil.rmtree(os.path.join(a3_path, project), True)
+                shutil.copytree(os.path.join(module_root, release_dir, project), os.path.join(a3_path, project))
+            except:
+                print_error("Could not copy files. Is Arma 3 running?")
+
+    copy_important_files(module_root_parent,os.path.join(release_dir, "@ace"))
+    cleanup_optionals(optionals_modules,optional_files)
 if __name__ == "__main__":
-	main(sys.argv)
+    main(sys.argv)
 input("Press Enter to continue...")

From 8a55c282e17eba4ae63957773597f3634ec335ee Mon Sep 17 00:00:00 2001
From: bux578 <github@jonathandavid.de>
Date: Tue, 21 Apr 2015 13:27:45 +0200
Subject: [PATCH 2/2] add repair module icon

---
 extras/assets/icons/Icons_Modules.psd         | Bin 2356619 -> 2326533 bytes
 .../png/Icon_Module/Icon_Module_Repair_ca.png | Bin 0 -> 1621 bytes
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 extras/assets/icons/png/Icon_Module/Icon_Module_Repair_ca.png

diff --git a/extras/assets/icons/Icons_Modules.psd b/extras/assets/icons/Icons_Modules.psd
index 9dcfa6a38b961c88def30c9f968c6a33349eb68f..968664452112dd74a054a33c3115fa92130ef8a2 100644
GIT binary patch
delta 23791
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
P-+t>5QIhgGTVDSQdR}wK

delta 46144
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

diff --git a/extras/assets/icons/png/Icon_Module/Icon_Module_Repair_ca.png b/extras/assets/icons/png/Icon_Module/Icon_Module_Repair_ca.png
new file mode 100644
index 0000000000000000000000000000000000000000..381c3d1c41147cdf234cb82a7490dc2afd54a567
GIT binary patch
literal 1621
zcmbVMdrT8|96ySqghfD+38;7+5+9MaSFW$K1xv5B4Hix5pgx$K?X{fH-q9YkWiwU~
z@ih~N4}37lVkRhIu!(4hj-m0f1tGc_6lL`>aT%KiG%C)pD_FNbjDPHMcfZGdKA-R7
z_x;}XmD$UtMkGW405Daj#SHAYH1LFnvG3z#$sjw#Gino)Ls=O&?j!)Eg<4O5Iy-J7
z3<PfRmRup^01#>=jV8vVUyhn7I}Z<Fcpf{=vH?Jz;h}MJ5y61#2^;B9K#x1Whd|Pz
zfbv9ofu2?og`~FJN#vAg8_nfKW|;-bNC)K}lohZO3=Vqi#SRzhQ9u*AC_4{q^C55o
z!W1c>NvBNum7t1p5}=5emTDHjFen!DU_>Mq!;3+o02cBE>@7}(5mY2bg%a@11+mtg
z7AtDNG;eIND+N@@Ff_{NyWMV{JB>#<ZG2cJlLa({!c-QK>hd}m+>`2XB?lES!ew@n
zG(%DjFrbL9r%D+G#Ckdzf}Pgu-wHciZxY3(jPJo|KFkyF?e;)i6KEG>ApUjZooJWQ
zOA~wp;i5{NX7)U+$w4xkyZ0Lk09kL)6;6^p6ucOt%%yh1!RRmr#D3vfNDC^KsH7r^
zM5&R&Vl@K8(o9ULR;H;XG6^h{A(EiRJGnv$BE~R*S_;cxSdAzVM4Aa_YBW-fNP;K@
zL9Wi>VsM9<2-+nBb|<(h&3n11%1Pi1<up=Maj*hb7E%o5Dx_#ol_TQ8U>@nPP;OV?
zcqXI82q#%aSTs(`4o>73CEsCRgUOTv1i@gLS{jI3rWU}eG_hKPiC_d4vvE&yE&nHH
zeAXF$pg8_hEWs_d0t3^xrOz(jHV@%oJI2Yj#vl1NYyiNi(qT%Y=hpC{-mDpT+$CGq
z`R=1~BUT-ms}0}NuvcrE0}ao7x>H{I`?WKw@y!dqn%^eehiVUmf3a;xg?trOG3RNY
zI%xz-gic+nOAqbES6SV*I`^f6-g<8O@bHq+t5;vu4S5G`gEx2Wlvz`Ap4|C-)Y~E5
ze>kF`6A&$&wc_GlK)K?9#@m*_S>$^N+)rASQ`LVXx-VMWhcqU(ZZGvcOY-LqoGnfG
zd3FP*5g=lAzKn&R0kKOz(3+AS9OpIyi+sPxz?e3_Y@j}D^eoS|Cj7y@IL_`K=+WW^
z&V_-pZr;rneOBZG8`qBxZ_e)L{?rQ8`_C31hA9Q8VY+M`C!b|jHvPJ*hC91zS>M`+
zr`u0%?W%(+$5UFyr*CbD{FaRI#~pc2J`cMSwa(vhlDPgT<XFR%V;iHtw%3MrZoD=4
zLp8F$`(<vF#NXU|&XzLv=Yh}KBED<=a$Zz_gq@BW>?+)Ie{})1hxVo<I%k#5Z;Cw4
zsd;jJs<lWEGUVqhj~$*{ZO2;{?wFZ;?6=+Pw#3YmU&=T*-Z9kvx+?1l+9LBOtlgR`
zoj$U*J-wF`igT&ezBz`T_RIN?n_9;!nqv{Jv~0@QJ><h9TRKO)bm;53=R<WrcKDB5
z#xh@p@sFmPL?w4|*V5$MGu+K5S|6{et;An^;@9QVvm=#DnlI<qSI5_sB;@)W@qgcq
z81sz2s=eJaH)Z$el-Ku-FMB@jk2Xa~UwE5_6nW!ut@IUin#mMZ=o_LhesjUM@a~Dz
zpU$h?VE~rpI{wNU898;JU|M2#O)dXSJbz31(Q}&)J%6!bAFw?w|D(JRBM{rQ;d+LS
g3~vO=1~!F+0jq1GA6{&!ofr7g>(tp;OJ+gEKe@hM1ONa4

literal 0
HcmV?d00001