"""Install or remove the patch in a Facade install. Needs only Python. python tools/install_patch.py [facade-install-folder] # install python tools/install_patch.py [facade-install-folder] --uninstall # put the originals back python tools/install_patch.py [facade-install-folder] --status install.bat in the repo root runs this with administrator rights, which writing into Program Files needs. Only the class files listed in patch_manifest.py are touched. Every replaced original is kept next to the new file as .orig, and --uninstall restores them and removes what was added. Safe to re-run. Restart the game afterwards; a running game keeps the classes it already loaded. """ import shutil import sys from facade_install import PREBUILT_CLASSES, from_args from original_parser import renamed_original_parser from patch_manifest import ADDED, GENERATED, PREBUILT, REPLACED def backup_of(path): return path.with_name(path.name + ".orig") def status(classes): backed_up = [name for name in REPLACED if backup_of(classes / name).is_file()] if not backed_up: return "not installed" if len(backed_up) == len(REPLACED) and all((classes / name).is_file() for name in ADDED + GENERATED): return "installed" return "partly installed (an older version of the patch?): run install again" def install(classes): missing = [name for name in PREBUILT if not (PREBUILT_CLASSES / name).is_file()] if missing: sys.exit("patch/prebuilt is missing %s; is the checkout complete?" % ", ".join(missing)) # Made from the player's own copy of the game, before anything is replaced. original_parser = renamed_original_parser(classes) for name in REPLACED: if not backup_of(classes / name).exists(): shutil.copy2(classes / name, backup_of(classes / name)) (classes / GENERATED[0]).write_bytes(original_parser) for name in PREBUILT: shutil.copy2(PREBUILT_CLASSES / name, classes / name) print("installed into %s" % classes) print("originals kept as *.orig; restart the game to load the new classes") def uninstall(classes): restored = 0 for name in REPLACED: backup = backup_of(classes / name) if backup.is_file(): shutil.copy2(backup, classes / name) backup.unlink() restored += 1 if restored == 0: sys.exit("not installed (no .orig backups)") for name in ADDED + GENERATED: (classes / name).unlink(missing_ok=True) print("restored %d original classes in %s" % (restored, classes)) def main(): classes = from_args(sys.argv).classes try: if "--status" in sys.argv: print(status(classes)) elif "--uninstall" in sys.argv: uninstall(classes) else: install(classes) except PermissionError: sys.exit("Windows will not let this process write to %s.\n" "Use install.bat, or a terminal opened with 'Run as administrator'." % classes) if __name__ == "__main__": main()