diff --git a/.gitignore b/.gitignore index e9910af0..316cf5be 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ *.apsave build -/build_factorio/ bundle/components.wxs dist README.html diff --git a/FactorioClient.py b/FactorioClient.py index 174a8d50..1be755c7 100644 --- a/FactorioClient.py +++ b/FactorioClient.py @@ -25,9 +25,9 @@ os.makedirs("logs", exist_ok=True) # Log to file in gui case if getattr(sys, "frozen", False) and not "--nogui" in sys.argv: logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, - filename=os.path.join("logs", "FactorioClient.txt"), filemode="w") + filename=os.path.join("logs", "FactorioClient.txt"), filemode="w", force=True) else: - logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO) + logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, force=True) logging.getLogger().addHandler(logging.FileHandler(os.path.join("logs", "FactorioClient.txt"), "w")) gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv @@ -299,7 +299,7 @@ async def main(args): if ctx.server and not ctx.server.socket.closed: await ctx.server.socket.close() - if ctx.server_task is not None: + if ctx.server_task: await ctx.server_task while ctx.input_requests > 0: @@ -352,7 +352,7 @@ if __name__ == '__main__': if not os.path.exists(bin_dir): raise FileNotFoundError(f"Path {bin_dir} does not exist or could not be accessed.") if not os.path.isdir(bin_dir): - raise FileNotFoundError(f"Path {bin_dir} is not a directory.") + raise NotADirectoryError(f"Path {bin_dir} is not a directory.") if not os.path.exists(executable): if os.path.exists(executable + ".exe"): executable = executable + ".exe" diff --git a/LttPClient.py b/LttPClient.py index bb89d838..6ddd5879 100644 --- a/LttPClient.py +++ b/LttPClient.py @@ -1,6 +1,8 @@ import argparse import atexit +import threading import time +import sys import multiprocessing import os import subprocess @@ -23,11 +25,22 @@ from NetUtils import * from worlds.alttp import Regions, Shops from worlds.alttp import Items import Utils -from CommonClient import CommonContext, server_loop, logger, console_loop, ClientCommandProcessor +from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor +snes_logger = logging.getLogger("SNES") from MultiServer import mark_raw +os.makedirs("logs", exist_ok=True) + +# Log to file in gui case +if getattr(sys, "frozen", False) and not "--nogui" in sys.argv: + logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, + filename=os.path.join("logs", "LttPClient.txt"), filemode="w", force=True) +else: + logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, force=True) + logging.getLogger().addHandler(logging.FileHandler(os.path.join("logs", "LttPClient.txt"), "w")) + class LttPCommandProcessor(ClientCommandProcessor): def _cmd_slow_mode(self, toggle: str = ""): @@ -71,6 +84,7 @@ class Context(CommonContext): self.snes_recv_queue = asyncio.Queue() self.snes_request_lock = asyncio.Lock() self.snes_write_buffer = [] + self.snes_connector_lock = threading.Lock() self.awaiting_rom = False self.rom = None @@ -91,7 +105,7 @@ class Context(CommonContext): await super(Context, self).server_auth(password_requested) if self.rom is None: self.awaiting_rom = True - logger.info( + snes_logger.info( 'No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)') return self.awaiting_rom = False @@ -420,19 +434,18 @@ def launch_sni(ctx: Context): sni_path = os.path.join(sni_path, file) if os.path.isfile(sni_path): - logger.info(f"Attempting to start {sni_path}") + snes_logger.info(f"Attempting to start {sni_path}") import subprocess subprocess.Popen(sni_path, cwd=os.path.dirname(sni_path), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) else: - logger.info( + snes_logger.info( f"Attempt to start SNI was aborted as path {sni_path} was not found, " f"please start it yourself if it is not running") async def _snes_connect(ctx: Context, address: str): address = f"ws://{address}" if "://" not in address else address - - logger.info("Connecting to SNI at %s ..." % address) + snes_logger.info("Connecting to SNI at %s ..." % address) seen_problems = set() succesful = False while not succesful: @@ -444,7 +457,7 @@ async def _snes_connect(ctx: Context, address: str): # only tell the user about new problems, otherwise silently lay in wait for a working connection if problem not in seen_problems: seen_problems.add(problem) - logger.error(f"Error connecting to SNI ({problem})") + snes_logger.error(f"Error connecting to SNI ({problem})") if len(seen_problems) == 1: # this is the first problem. Let's try launching SNI if it isn't already running @@ -467,7 +480,7 @@ async def get_snes_devices(ctx: Context): devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None if not devices: - logger.info('No SNES device found. Please connect a SNES device to SNI.') + snes_logger.info('No SNES device found. Please connect a SNES device to SNI.') while not devices: await asyncio.sleep(1) await socket.send(dumps(DeviceList_Request)) @@ -482,7 +495,7 @@ async def get_snes_devices(ctx: Context): async def snes_connect(ctx: Context, address): global SNES_RECONNECT_DELAY if ctx.snes_socket is not None and ctx.snes_state == SNESState.SNES_CONNECTED: - logger.error('Already connected to snes') + snes_logger.error('Already connected to snes') return recv_task = None @@ -505,7 +518,7 @@ async def snes_connect(ctx: Context, address): await snes_disconnect(ctx) return - logger.info("Attaching to " + device) + snes_logger.info("Attaching to " + device) Attach_Request = { "Opcode": "Attach", @@ -530,9 +543,9 @@ async def snes_connect(ctx: Context, address): ctx.snes_socket = None ctx.snes_state = SNESState.SNES_DISCONNECTED if not ctx.snes_reconnect_address: - logger.error("Error connecting to snes (%s)" % e) + snes_logger.error("Error connecting to snes (%s)" % e) else: - logger.error(f"Error connecting to snes, attempt again in {SNES_RECONNECT_DELAY}s") + snes_logger.error(f"Error connecting to snes, attempt again in {SNES_RECONNECT_DELAY}s") asyncio.create_task(snes_autoreconnect(ctx)) SNES_RECONNECT_DELAY *= 2 @@ -559,11 +572,11 @@ async def snes_recv_loop(ctx: Context): try: async for msg in ctx.snes_socket: ctx.snes_recv_queue.put_nowait(msg) - logger.warning("Snes disconnected") + snes_logger.warning("Snes disconnected") except Exception as e: if not isinstance(e, websockets.WebSocketException): - logger.exception(e) - logger.error("Lost connection to the snes, type /snes to reconnect") + snes_logger.exception(e) + snes_logger.error("Lost connection to the snes, type /snes to reconnect") finally: socket, ctx.snes_socket = ctx.snes_socket, None if socket is not None and not socket.closed: @@ -576,7 +589,7 @@ async def snes_recv_loop(ctx: Context): ctx.rom = None if ctx.snes_reconnect_address: - logger.info(f"...reconnecting in {SNES_RECONNECT_DELAY}s") + snes_logger.info(f"...reconnecting in {SNES_RECONNECT_DELAY}s") asyncio.create_task(snes_autoreconnect(ctx)) @@ -605,10 +618,10 @@ async def snes_read(ctx: Context, address, size): break if len(data) != size: - logger.error('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data))) + snes_logger.error('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data))) if len(data): - logger.error(str(data)) - logger.warning('Communication Failure with SNI') + snes_logger.error(str(data)) + snes_logger.warning('Communication Failure with SNI') if ctx.snes_socket is not None and not ctx.snes_socket.closed: await ctx.snes_socket.close() return None @@ -634,7 +647,7 @@ async def snes_write(ctx: Context, write_list): await ctx.snes_socket.send(dumps(PutAddress_Request)) await ctx.snes_socket.send(data) else: - logger.warning(f"Could not send data to SNES: {data}") + snes_logger.warning(f"Could not send data to SNES: {data}") except websockets.ConnectionClosed: return False @@ -673,7 +686,7 @@ async def track_locations(ctx: Context, roomid, roomdata): new_locations.append(location_id) ctx.locations_checked.add(location_id) location = ctx.location_name_getter(location_id) - logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') try: if roomid in location_shop_ids: @@ -682,7 +695,7 @@ async def track_locations(ctx: Context, roomid, roomdata): if int(b) and (Shops.SHOP_ID_START + cnt) not in ctx.locations_checked: new_check(Shops.SHOP_ID_START + cnt) except Exception as e: - logger.info(f"Exception: {e}") + snes_logger.info(f"Exception: {e}") for location_id, (loc_roomid, loc_mask) in location_table_uw_id.items(): try: @@ -691,7 +704,7 @@ async def track_locations(ctx: Context, roomid, roomdata): roomdata << 4) & loc_mask != 0: new_check(location_id) except Exception as e: - logger.exception(f"Exception: {e}") + snes_logger.exception(f"Exception: {e}") uw_begin = 0x129 ow_end = uw_end = 0 @@ -774,7 +787,7 @@ async def game_watcher(ctx: Context): await ctx.server_auth(False) if ctx.auth and ctx.auth != ctx.rom: - logger.warning("ROM change detected, please reconnect to the multiworld server") + snes_logger.warning("ROM change detected, please reconnect to the multiworld server") await ctx.disconnect() gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) @@ -857,6 +870,8 @@ async def main(): parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical']) parser.add_argument('--founditems', default=False, action='store_true', help='Show items found by other players for themselves.') + if not Utils.is_frozen(): # Frozen state has no cmd window in the first place + parser.add_argument('--nogui', default=False, action='store_true', help="Turns off Client GUI.") args = parser.parse_args() logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) if args.diff_file: @@ -875,22 +890,33 @@ async def main(): asyncio.create_task(run_game(adjustedromfile if adjusted else romfile)) ctx = Context(args.snes, args.connect, args.password) - input_task = asyncio.create_task(console_loop(ctx), name="Input") - if ctx.server_task is None: ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") - asyncio.create_task(snes_connect(ctx, ctx.snes_address)) + + if Utils.is_frozen() or "--nogui" not in sys.argv: + input_task = None + from kvui import LttPManager + ui_app = LttPManager(ctx) + ctx.ui = ui_app + ui_task = asyncio.create_task(ui_app.async_run(), name="UI") + else: + input_task = asyncio.create_task(console_loop(ctx), name="Input") + ui_task = None + + snes_connect_task = asyncio.create_task(snes_connect(ctx, ctx.snes_address)) watcher_task = asyncio.create_task(game_watcher(ctx), name="GameWatcher") await ctx.exit_event.wait() + if snes_connect_task: + snes_connect_task.cancel() ctx.server_address = None ctx.snes_reconnect_address = None await watcher_task - if ctx.server is not None and not ctx.server.socket.closed: + if ctx.server and not ctx.server.socket.closed: await ctx.server.socket.close() - if ctx.server_task is not None: + if ctx.server_task: await ctx.server_task if ctx.snes_socket is not None and not ctx.snes_socket.closed: @@ -900,7 +926,11 @@ async def main(): ctx.input_queue.put_nowait(None) ctx.input_requests -= 1 - await input_task + if ui_task: + await ui_task + + if input_task: + input_task.cancel() if __name__ == '__main__': diff --git a/factorio_inno_setup_38.iss b/factorio_inno_setup_38.iss deleted file mode 100644 index 0cb1fd18..00000000 --- a/factorio_inno_setup_38.iss +++ /dev/null @@ -1,80 +0,0 @@ -#define sourcepath "build_factorio\exe.win-amd64-3.8\" -#define MyAppName "Archipelago Factorio Client" -#define MyAppExeName "ArchipelagoGraphicalFactorioClient.exe" -#define MyAppIcon "data/icon.ico" - -[Setup] -; NOTE: The value of AppId uniquely identifies this application. -; Do not use the same AppId value in installers for other applications. -AppId={{D13CEBD0-F1D5-4435-A4A6-5243F934613F}} -AppName={#MyAppName} -AppVerName={#MyAppName} -DefaultDirName={commonappdata}\{#MyAppName} -DisableProgramGroupPage=yes -DefaultGroupName=Archipelago -OutputDir=setups -OutputBaseFilename=Setup {#MyAppName} -Compression=lzma2 -SolidCompression=yes -LZMANumBlockThreads=8 -ArchitecturesInstallIn64BitMode=x64 -ChangesAssociations=yes -ArchitecturesAllowed=x64 -AllowNoIcons=yes -SetupIconFile={#MyAppIcon} -UninstallDisplayIcon={app}\{#MyAppExeName} -SignTool= signtool -LicenseFile= LICENSE -WizardStyle= modern -SetupLogging=yes - -[Languages] -Name: "english"; MessagesFile: "compiler:Default.isl" - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; - - -[Dirs] -NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; - -[Files] -Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -; NOTE: Don't use "Flags: ignoreversion" on any shared system files - -[Icons] -Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; -Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon - -[Run] -Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." - -[UninstallDelete] -Type: dirifempty; Name: "{app}" - - -[Code] -// See: https://stackoverflow.com/a/51614652/2287576 -function IsVCRedist64BitNeeded(): boolean; -var - strVersion: string; -begin - if (RegQueryStringValue(HKEY_LOCAL_MACHINE, - 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then - begin - // Is the installed version at least the packaged one ? - Log('VC Redist x64 Version : found ' + strVersion); - Result := (CompareStr(strVersion, 'v14.29.30037') < 0); - end - else - begin - // Not even an old version installed - Log('VC Redist x64 is not already installed'); - Result := True; - end; -end; - - diff --git a/inno_setup_38.iss b/inno_setup_38.iss index 50d9de1e..c6788b78 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -1,6 +1,6 @@ #define sourcepath "build\exe.win-amd64-3.8\" #define MyAppName "Archipelago" -#define MyAppExeName "ArchipelagoLttPClient.exe" +#define MyAppExeName "ArchipelagoServer.exe" #define MyAppIcon "data/icon.ico" [Setup] @@ -34,40 +34,65 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; +[Types] +Name: "full"; Description: "Full installation" +Name: "hosting"; Description: "Installation for hosting purposes" +Name: "playing"; Description: "Installation for playing purposes" +Name: "custom"; Description: "Custom installation"; Flags: iscustom + +[Components] +Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed +Name: "generator"; Description: "Generator"; Types: full hosting +Name: "server"; Description: "Server"; Types: full hosting +Name: "client"; Description: "Clients"; Types: full playing +Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing hosting +Name: "client/factorio"; Description: "Factorio"; Types: full playing [Dirs] NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; [Files] -Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external -Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/lttp or server +Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, *exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#sourcepath}\SNI"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/lttp +Source: "{#sourcepath}\EnemizerCLI"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator + +Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator +Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server +Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio +Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp +Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; +Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server +Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp +Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server +Name: "{commondesktop}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Tasks: desktopicon; Components: client/lttp +Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio [Run] + Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..." +Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: client/lttp or server [UninstallDelete] Type: dirifempty; Name: "{app}" [Registry] -Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" +Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/lttp +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/lttp +Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: ""; Components: client/lttp +Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: ""; Components: client/lttp -Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: "" +Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: server +Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: server +Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; Components: server +Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: server diff --git a/inno_setup_39.iss b/inno_setup_39.iss deleted file mode 100644 index a422021b..00000000 --- a/inno_setup_39.iss +++ /dev/null @@ -1,141 +0,0 @@ -#define sourcepath "build\exe.win-amd64-3.9\" -#define MyAppName "Archipelago" -#define MyAppExeName "ArchipelagoLttPClient.exe" -#define MyAppIcon "data/icon.ico" - -[Setup] -; NOTE: The value of AppId uniquely identifies this application. -; Do not use the same AppId value in installers for other applications. -AppId={{918BA46A-FAB8-460C-9DFF-AE691E1C865B}} -AppName={#MyAppName} -AppVerName={#MyAppName} -DefaultDirName={commonappdata}\{#MyAppName} -DisableProgramGroupPage=yes -DefaultGroupName=Archipelago -OutputDir=setups -OutputBaseFilename=Setup {#MyAppName} -Compression=lzma2 -SolidCompression=yes -LZMANumBlockThreads=8 -ArchitecturesInstallIn64BitMode=x64 -ChangesAssociations=yes -ArchitecturesAllowed=x64 -AllowNoIcons=yes -SetupIconFile={#MyAppIcon} -UninstallDisplayIcon={app}\{#MyAppExeName} -SignTool= signtool -LicenseFile= LICENSE -WizardStyle= modern -SetupLogging=yes - -[Languages] -Name: "english"; MessagesFile: "compiler:Default.isl" - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; - - -[Dirs] -NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; - -[Files] -Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external -Source: "{#sourcepath}*"; Excludes: "*.sfc, *.log, data\sprites\alttpr"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall -; NOTE: Don't use "Flags: ignoreversion" on any shared system files - -[Icons] -Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; -Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon - -[Run] -Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..." - -[UninstallDelete] -Type: dirifempty; Name: "{app}" - -[Registry] - -Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" - -Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: "" - - - -[Code] -// See: https://stackoverflow.com/a/51614652/2287576 -function IsVCRedist64BitNeeded(): boolean; -var - strVersion: string; -begin - if (RegQueryStringValue(HKEY_LOCAL_MACHINE, - 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', strVersion)) then - begin - // Is the installed version at least the packaged one ? - Log('VC Redist x64 Version : found ' + strVersion); - Result := (CompareStr(strVersion, 'v14.29.30037') < 0); - end - else - begin - // Not even an old version installed - Log('VC Redist x64 is not already installed'); - Result := True; - end; -end; - -var ROMFilePage: TInputFileWizardPage; -var R : longint; -var rom: string; - -procedure InitializeWizard(); -begin - rom := FileSearch('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', WizardDirValue()); - if Length(rom) > 0 then - begin - log('existing ROM found'); - log(IntToStr(CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173'))); - if CompareStr(GetMD5OfFile(rom), '03a63945398191337e896e5771f77173') = 0 then - begin - log('existing ROM verified'); - exit; - end; - log('existing ROM failed verification'); - end; - rom := '' - ROMFilePage := - CreateInputFilePage( - wpLicense, - 'Select ROM File', - 'Where is your Zelda no Densetsu - Kamigami no Triforce (Japan).sfc located?', - 'Select the file, then click Next.'); - - ROMFilePage.Add( - 'Location of ROM file:', - 'SNES ROM files|*.sfc|All files|*.*', - '.sfc'); -end; - -function GetROMPath(Param: string): string; -begin - if Length(rom) > 0 then - Result := rom - else if Assigned(RomFilePage) then - begin - R := CompareStr(GetMD5OfFile(ROMFilePage.Values[0]), '03a63945398191337e896e5771f77173') - if R <> 0 then - MsgBox('ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); - - Result := ROMFilePage.Values[0] - end - else - Result := ''; - end; diff --git a/kvui.py b/kvui.py index db69fd60..5c2f60f7 100644 --- a/kvui.py +++ b/kvui.py @@ -83,9 +83,12 @@ class FactorioManager(GameManager): ] title = "Archipelago Factorio Client" - def __init__(self, ctx): - super(FactorioManager, self).__init__(ctx) - +class LttPManager(GameManager): + logging_pairs = [ + ("Client", "Archipelago"), + ("SNES", "SNES"), + ] + title = "Archipelago LttP Client" class LogtoUI(logging.Handler): def __init__(self, on_log): diff --git a/setup.py b/setup.py index 009d5705..b081336e 100644 --- a/setup.py +++ b/setup.py @@ -4,12 +4,13 @@ import sys import sysconfig from pathlib import Path import cx_Freeze +from kivy_deps import sdl2, glew is_64bits = sys.maxsize > 2 ** 32 -folder = "exe.{platform}-{version}".format(platform=sysconfig.get_platform(), +arch_folder = "exe.{platform}-{version}".format(platform=sysconfig.get_platform(), version=sysconfig.get_python_version()) -buildfolder = Path("build", folder) +buildfolder = Path("build", arch_folder) sbuildfolder = str(buildfolder) libfolder = Path(buildfolder, "lib") library = Path(libfolder, "library.zip") @@ -56,20 +57,31 @@ def manifest_creation(folder): print("Created Manifest") +def remove_sprites_from_folder(folder): + for file in os.listdir(folder): + if file != ".gitignore": + os.remove(folder / file) + + scripts = { - "LttPClient.py": "ArchipelagoLttPClient", - "MultiServer.py": "ArchipelagoServer", - "Generate.py": "ArchipelagoGenerate", - "LttPAdjuster.py": "ArchipelagoLttPAdjuster" + # Core + "MultiServer.py": ("ArchipelagoServer", False), + "Generate.py": ("ArchipelagoGenerate", False), + # LttP + "LttPClient.py": ("ArchipelagoLttPClient", True), + "LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True), + # Factorio + "FactorioClient.py": ("ArchipelagoFactorioClient", True), } exes = [] -for script, scriptname in scripts.items(): +for script, (scriptname, gui) in scripts.items(): exes.append(cx_Freeze.Executable( script=script, target_name=scriptname + ("" if sys.platform == "linux" else ".exe"), icon=icon, + base="Win32GUI" if sys.platform == "win32" and gui else None )) import datetime @@ -83,14 +95,14 @@ cx_Freeze.setup( executables=exes, options={ "build_exe": { - "packages": ["websockets", "worlds"], + "packages": ["websockets", "worlds", "kivy"], "includes": [], "excludes": ["numpy", "Cython", "PySide2", "PIL", "pandas"], "zip_include_packages": ["*"], - "zip_exclude_packages": ["worlds"], + "zip_exclude_packages": ["worlds", "kivy"], "include_files": [], - "include_msvcr": True, + "include_msvcr": False, "replace_paths": [("*", "")], "optimize": 1, "build_exe": buildfolder @@ -112,6 +124,9 @@ def installfile(path, keep_content=False): else: print('Warning,', path, 'not found') +for folder in sdl2.dep_bins+glew.dep_bins: + shutil.copytree(folder, libfolder, dirs_exist_ok=True) + print('copying', folder, '->', libfolder) extra_data = ["LICENSE", "data", "EnemizerCLI", "host.yaml", "SNI", "meta.yaml"] @@ -138,76 +153,6 @@ if signtool: print(f"Signing SNI") os.system(signtool + os.path.join(buildfolder, "SNI", "SNI.exe")) -alttpr_sprites_folder = buildfolder / "data" / "sprites" / "alttpr" -for file in os.listdir(alttpr_sprites_folder): - if file != ".gitignore": - os.remove(alttpr_sprites_folder / file) - -manifest_creation(buildfolder) - -buildfolder = Path("build_factorio", folder) -sbuildfolder = str(buildfolder) -libfolder = Path(buildfolder, "lib") -library = Path(libfolder, "library.zip") -print("Outputting Factorio Client to: " + sbuildfolder) - -os.makedirs(buildfolder, exist_ok=True) - - -exes = [ - cx_Freeze.Executable( - script="FactorioClient.py", - target_name="ArchipelagoFactorioClient" + ("" if sys.platform == "linux" else ".exe"), - icon=icon, - base="Win32GUI" if sys.platform == "win32" else None -)] - -import datetime - -buildtime = datetime.datetime.utcnow() - -cx_Freeze.setup( - name="Archipelago Factorio Client", - version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}", - description="Archipelago Factorio Client", - executables=exes, - options={ - "build_exe": { - "packages": ["websockets", "kivy", "worlds"], - "includes": [], - "excludes": ["numpy", "Cython", "PySide2", "PIL", - "pandas"], - "zip_include_packages": ["*"], - "zip_exclude_packages": ["kivy", "worlds"], - "include_files": [], - "include_msvcr": True, - "replace_paths": [("*", "")], - "optimize": 1, - "build_exe": buildfolder - }, - }, -) - - -extra_data = ["LICENSE", "data", "host.yaml", "meta.yaml"] -from kivy_deps import sdl2, glew -for folder in sdl2.dep_bins+glew.dep_bins: - shutil.copytree(folder, buildfolder, dirs_exist_ok=True) -for data in extra_data: - installfile(Path(data)) - - -os.makedirs(buildfolder / "Players", exist_ok=True) -shutil.copyfile("playerSettings.yaml", buildfolder / "Players" / "weightedSettings.yaml") - -if signtool: - for exe in exes: - print(f"Signing {exe.target_name}") - os.system(signtool + os.path.join(buildfolder, exe.target_name)) - -alttpr_sprites_folder = buildfolder / "data" / "sprites" / "alttpr" -for file in os.listdir(alttpr_sprites_folder): - if file != ".gitignore": - os.remove(alttpr_sprites_folder / file) +remove_sprites_from_folder(buildfolder / "data" / "sprites" / "alttpr") manifest_creation(buildfolder)