unify clients and setup
This commit is contained in:
		
							parent
							
								
									096e682b18
								
							
						
					
					
						commit
						66514ec607
					
				| 
						 | 
					@ -16,7 +16,6 @@
 | 
				
			||||||
*.apsave
 | 
					*.apsave
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build
 | 
					build
 | 
				
			||||||
/build_factorio/
 | 
					 | 
				
			||||||
bundle/components.wxs
 | 
					bundle/components.wxs
 | 
				
			||||||
dist
 | 
					dist
 | 
				
			||||||
README.html
 | 
					README.html
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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__':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								kvui.py
								
								
								
								
							
							
						
						
									
										9
									
								
								kvui.py
								
								
								
								
							| 
						 | 
					@ -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
								
								
								
								
							
							
						
						
									
										107
									
								
								setup.py
								
								
								
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue