unify clients and setup

This commit is contained in:
Fabian Dill 2021-07-31 00:03:48 +02:00
parent 096e682b18
commit 66514ec607
8 changed files with 136 additions and 355 deletions

1
.gitignore vendored
View File

@ -16,7 +16,6 @@
*.apsave *.apsave
build build
/build_factorio/
bundle/components.wxs bundle/components.wxs
dist dist
README.html README.html

View File

@ -25,9 +25,9 @@ os.makedirs("logs", exist_ok=True)
# Log to file in gui case # Log to file in gui case
if getattr(sys, "frozen", False) and not "--nogui" in sys.argv: if getattr(sys, "frozen", False) and not "--nogui" in sys.argv:
logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, 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: 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")) logging.getLogger().addHandler(logging.FileHandler(os.path.join("logs", "FactorioClient.txt"), "w"))
gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv 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: if ctx.server and not ctx.server.socket.closed:
await ctx.server.socket.close() await ctx.server.socket.close()
if ctx.server_task is not None: if ctx.server_task:
await ctx.server_task await ctx.server_task
while ctx.input_requests > 0: while ctx.input_requests > 0:
@ -352,7 +352,7 @@ if __name__ == '__main__':
if not os.path.exists(bin_dir): if not os.path.exists(bin_dir):
raise FileNotFoundError(f"Path {bin_dir} does not exist or could not be accessed.") raise FileNotFoundError(f"Path {bin_dir} does not exist or could not be accessed.")
if not os.path.isdir(bin_dir): 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 not os.path.exists(executable):
if os.path.exists(executable + ".exe"): if os.path.exists(executable + ".exe"):
executable = executable + ".exe" executable = executable + ".exe"

View File

@ -1,6 +1,8 @@
import argparse import argparse
import atexit import atexit
import threading
import time import time
import sys
import multiprocessing import multiprocessing
import os import os
import subprocess import subprocess
@ -23,11 +25,22 @@ from NetUtils import *
from worlds.alttp import Regions, Shops from worlds.alttp import Regions, Shops
from worlds.alttp import Items from worlds.alttp import Items
import Utils 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 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): class LttPCommandProcessor(ClientCommandProcessor):
def _cmd_slow_mode(self, toggle: str = ""): def _cmd_slow_mode(self, toggle: str = ""):
@ -71,6 +84,7 @@ class Context(CommonContext):
self.snes_recv_queue = asyncio.Queue() self.snes_recv_queue = asyncio.Queue()
self.snes_request_lock = asyncio.Lock() self.snes_request_lock = asyncio.Lock()
self.snes_write_buffer = [] self.snes_write_buffer = []
self.snes_connector_lock = threading.Lock()
self.awaiting_rom = False self.awaiting_rom = False
self.rom = None self.rom = None
@ -91,7 +105,7 @@ class Context(CommonContext):
await super(Context, self).server_auth(password_requested) await super(Context, self).server_auth(password_requested)
if self.rom is None: if self.rom is None:
self.awaiting_rom = True self.awaiting_rom = True
logger.info( snes_logger.info(
'No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)') 'No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)')
return return
self.awaiting_rom = False self.awaiting_rom = False
@ -420,19 +434,18 @@ def launch_sni(ctx: Context):
sni_path = os.path.join(sni_path, file) sni_path = os.path.join(sni_path, file)
if os.path.isfile(sni_path): 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 import subprocess
subprocess.Popen(sni_path, cwd=os.path.dirname(sni_path), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.Popen(sni_path, cwd=os.path.dirname(sni_path), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
else: else:
logger.info( snes_logger.info(
f"Attempt to start SNI was aborted as path {sni_path} was not found, " f"Attempt to start SNI was aborted as path {sni_path} was not found, "
f"please start it yourself if it is not running") f"please start it yourself if it is not running")
async def _snes_connect(ctx: Context, address: str): async def _snes_connect(ctx: Context, address: str):
address = f"ws://{address}" if "://" not in address else address address = f"ws://{address}" if "://" not in address else address
snes_logger.info("Connecting to SNI at %s ..." % address)
logger.info("Connecting to SNI at %s ..." % address)
seen_problems = set() seen_problems = set()
succesful = False succesful = False
while not succesful: 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 # only tell the user about new problems, otherwise silently lay in wait for a working connection
if problem not in seen_problems: if problem not in seen_problems:
seen_problems.add(problem) 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: if len(seen_problems) == 1:
# this is the first problem. Let's try launching SNI if it isn't already running # 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 devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None
if not devices: 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: while not devices:
await asyncio.sleep(1) await asyncio.sleep(1)
await socket.send(dumps(DeviceList_Request)) await socket.send(dumps(DeviceList_Request))
@ -482,7 +495,7 @@ async def get_snes_devices(ctx: Context):
async def snes_connect(ctx: Context, address): async def snes_connect(ctx: Context, address):
global SNES_RECONNECT_DELAY global SNES_RECONNECT_DELAY
if ctx.snes_socket is not None and ctx.snes_state == SNESState.SNES_CONNECTED: 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 return
recv_task = None recv_task = None
@ -505,7 +518,7 @@ async def snes_connect(ctx: Context, address):
await snes_disconnect(ctx) await snes_disconnect(ctx)
return return
logger.info("Attaching to " + device) snes_logger.info("Attaching to " + device)
Attach_Request = { Attach_Request = {
"Opcode": "Attach", "Opcode": "Attach",
@ -530,9 +543,9 @@ async def snes_connect(ctx: Context, address):
ctx.snes_socket = None ctx.snes_socket = None
ctx.snes_state = SNESState.SNES_DISCONNECTED ctx.snes_state = SNESState.SNES_DISCONNECTED
if not ctx.snes_reconnect_address: if not ctx.snes_reconnect_address:
logger.error("Error connecting to snes (%s)" % e) snes_logger.error("Error connecting to snes (%s)" % e)
else: 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)) asyncio.create_task(snes_autoreconnect(ctx))
SNES_RECONNECT_DELAY *= 2 SNES_RECONNECT_DELAY *= 2
@ -559,11 +572,11 @@ async def snes_recv_loop(ctx: Context):
try: try:
async for msg in ctx.snes_socket: async for msg in ctx.snes_socket:
ctx.snes_recv_queue.put_nowait(msg) ctx.snes_recv_queue.put_nowait(msg)
logger.warning("Snes disconnected") snes_logger.warning("Snes disconnected")
except Exception as e: except Exception as e:
if not isinstance(e, websockets.WebSocketException): if not isinstance(e, websockets.WebSocketException):
logger.exception(e) snes_logger.exception(e)
logger.error("Lost connection to the snes, type /snes to reconnect") snes_logger.error("Lost connection to the snes, type /snes to reconnect")
finally: finally:
socket, ctx.snes_socket = ctx.snes_socket, None socket, ctx.snes_socket = ctx.snes_socket, None
if socket is not None and not socket.closed: if socket is not None and not socket.closed:
@ -576,7 +589,7 @@ async def snes_recv_loop(ctx: Context):
ctx.rom = None ctx.rom = None
if ctx.snes_reconnect_address: 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)) asyncio.create_task(snes_autoreconnect(ctx))
@ -605,10 +618,10 @@ async def snes_read(ctx: Context, address, size):
break break
if len(data) != size: 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): if len(data):
logger.error(str(data)) snes_logger.error(str(data))
logger.warning('Communication Failure with SNI') snes_logger.warning('Communication Failure with SNI')
if ctx.snes_socket is not None and not ctx.snes_socket.closed: if ctx.snes_socket is not None and not ctx.snes_socket.closed:
await ctx.snes_socket.close() await ctx.snes_socket.close()
return None 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(dumps(PutAddress_Request))
await ctx.snes_socket.send(data) await ctx.snes_socket.send(data)
else: 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: except websockets.ConnectionClosed:
return False return False
@ -673,7 +686,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
new_locations.append(location_id) new_locations.append(location_id)
ctx.locations_checked.add(location_id) ctx.locations_checked.add(location_id)
location = ctx.location_name_getter(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: try:
if roomid in location_shop_ids: 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: if int(b) and (Shops.SHOP_ID_START + cnt) not in ctx.locations_checked:
new_check(Shops.SHOP_ID_START + cnt) new_check(Shops.SHOP_ID_START + cnt)
except Exception as e: 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(): for location_id, (loc_roomid, loc_mask) in location_table_uw_id.items():
try: try:
@ -691,7 +704,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
roomdata << 4) & loc_mask != 0: roomdata << 4) & loc_mask != 0:
new_check(location_id) new_check(location_id)
except Exception as e: except Exception as e:
logger.exception(f"Exception: {e}") snes_logger.exception(f"Exception: {e}")
uw_begin = 0x129 uw_begin = 0x129
ow_end = uw_end = 0 ow_end = uw_end = 0
@ -774,7 +787,7 @@ async def game_watcher(ctx: Context):
await ctx.server_auth(False) await ctx.server_auth(False)
if ctx.auth and ctx.auth != ctx.rom: 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() await ctx.disconnect()
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) 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('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
parser.add_argument('--founditems', default=False, action='store_true', parser.add_argument('--founditems', default=False, action='store_true',
help='Show items found by other players for themselves.') 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() args = parser.parse_args()
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
if args.diff_file: if args.diff_file:
@ -875,22 +890,33 @@ async def main():
asyncio.create_task(run_game(adjustedromfile if adjusted else romfile)) asyncio.create_task(run_game(adjustedromfile if adjusted else romfile))
ctx = Context(args.snes, args.connect, args.password) ctx = Context(args.snes, args.connect, args.password)
input_task = asyncio.create_task(console_loop(ctx), name="Input")
if ctx.server_task is None: if ctx.server_task is None:
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") 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") watcher_task = asyncio.create_task(game_watcher(ctx), name="GameWatcher")
await ctx.exit_event.wait() await ctx.exit_event.wait()
if snes_connect_task:
snes_connect_task.cancel()
ctx.server_address = None ctx.server_address = None
ctx.snes_reconnect_address = None ctx.snes_reconnect_address = None
await watcher_task 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() await ctx.server.socket.close()
if ctx.server_task is not None: if ctx.server_task:
await ctx.server_task await ctx.server_task
if ctx.snes_socket is not None and not ctx.snes_socket.closed: 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_queue.put_nowait(None)
ctx.input_requests -= 1 ctx.input_requests -= 1
await input_task if ui_task:
await ui_task
if input_task:
input_task.cancel()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -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;

View File

@ -1,6 +1,6 @@
#define sourcepath "build\exe.win-amd64-3.8\" #define sourcepath "build\exe.win-amd64-3.8\"
#define MyAppName "Archipelago" #define MyAppName "Archipelago"
#define MyAppExeName "ArchipelagoLttPClient.exe" #define MyAppExeName "ArchipelagoServer.exe"
#define MyAppIcon "data/icon.ico" #define MyAppIcon "data/icon.ico"
[Setup] [Setup]
@ -34,40 +34,65 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks] [Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; 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] [Dirs]
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
[Files] [Files]
Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external 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"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 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 Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; 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} 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] [Run]
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." 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] [UninstallDelete]
Type: dirifempty; Name: "{app}" Type: dirifempty; Name: "{app}"
[Registry] [Registry]
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; 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: "" 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: "" 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: "" 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: ".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: "" 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: "" 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: "" Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: server

View File

@ -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;

View File

@ -83,9 +83,12 @@ class FactorioManager(GameManager):
] ]
title = "Archipelago Factorio Client" title = "Archipelago Factorio Client"
def __init__(self, ctx): class LttPManager(GameManager):
super(FactorioManager, self).__init__(ctx) logging_pairs = [
("Client", "Archipelago"),
("SNES", "SNES"),
]
title = "Archipelago LttP Client"
class LogtoUI(logging.Handler): class LogtoUI(logging.Handler):
def __init__(self, on_log): def __init__(self, on_log):

107
setup.py
View File

@ -4,12 +4,13 @@ import sys
import sysconfig import sysconfig
from pathlib import Path from pathlib import Path
import cx_Freeze import cx_Freeze
from kivy_deps import sdl2, glew
is_64bits = sys.maxsize > 2 ** 32 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()) version=sysconfig.get_python_version())
buildfolder = Path("build", folder) buildfolder = Path("build", arch_folder)
sbuildfolder = str(buildfolder) sbuildfolder = str(buildfolder)
libfolder = Path(buildfolder, "lib") libfolder = Path(buildfolder, "lib")
library = Path(libfolder, "library.zip") library = Path(libfolder, "library.zip")
@ -56,20 +57,31 @@ def manifest_creation(folder):
print("Created Manifest") print("Created Manifest")
def remove_sprites_from_folder(folder):
for file in os.listdir(folder):
if file != ".gitignore":
os.remove(folder / file)
scripts = { scripts = {
"LttPClient.py": "ArchipelagoLttPClient", # Core
"MultiServer.py": "ArchipelagoServer", "MultiServer.py": ("ArchipelagoServer", False),
"Generate.py": "ArchipelagoGenerate", "Generate.py": ("ArchipelagoGenerate", False),
"LttPAdjuster.py": "ArchipelagoLttPAdjuster" # LttP
"LttPClient.py": ("ArchipelagoLttPClient", True),
"LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True),
# Factorio
"FactorioClient.py": ("ArchipelagoFactorioClient", True),
} }
exes = [] exes = []
for script, scriptname in scripts.items(): for script, (scriptname, gui) in scripts.items():
exes.append(cx_Freeze.Executable( exes.append(cx_Freeze.Executable(
script=script, script=script,
target_name=scriptname + ("" if sys.platform == "linux" else ".exe"), target_name=scriptname + ("" if sys.platform == "linux" else ".exe"),
icon=icon, icon=icon,
base="Win32GUI" if sys.platform == "win32" and gui else None
)) ))
import datetime import datetime
@ -83,14 +95,14 @@ cx_Freeze.setup(
executables=exes, executables=exes,
options={ options={
"build_exe": { "build_exe": {
"packages": ["websockets", "worlds"], "packages": ["websockets", "worlds", "kivy"],
"includes": [], "includes": [],
"excludes": ["numpy", "Cython", "PySide2", "PIL", "excludes": ["numpy", "Cython", "PySide2", "PIL",
"pandas"], "pandas"],
"zip_include_packages": ["*"], "zip_include_packages": ["*"],
"zip_exclude_packages": ["worlds"], "zip_exclude_packages": ["worlds", "kivy"],
"include_files": [], "include_files": [],
"include_msvcr": True, "include_msvcr": False,
"replace_paths": [("*", "")], "replace_paths": [("*", "")],
"optimize": 1, "optimize": 1,
"build_exe": buildfolder "build_exe": buildfolder
@ -112,6 +124,9 @@ def installfile(path, keep_content=False):
else: else:
print('Warning,', path, 'not found') 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"] extra_data = ["LICENSE", "data", "EnemizerCLI", "host.yaml", "SNI", "meta.yaml"]
@ -138,76 +153,6 @@ if signtool:
print(f"Signing SNI") print(f"Signing SNI")
os.system(signtool + os.path.join(buildfolder, "SNI", "SNI.exe")) os.system(signtool + os.path.join(buildfolder, "SNI", "SNI.exe"))
alttpr_sprites_folder = buildfolder / "data" / "sprites" / "alttpr" remove_sprites_from_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)
manifest_creation(buildfolder) manifest_creation(buildfolder)