first version of a Factorio Graphical Client
This commit is contained in:
		
							parent
							
								
									4b04f2b918
								
							
						
					
					
						commit
						bc028a63cd
					
				| 
						 | 
				
			
			@ -0,0 +1,342 @@
 | 
			
		|||
import os
 | 
			
		||||
import logging
 | 
			
		||||
logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, filename="log.txt", filemode="w")
 | 
			
		||||
import json
 | 
			
		||||
import string
 | 
			
		||||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
 | 
			
		||||
os.environ["KIVY_NO_FILELOG"] = "1"
 | 
			
		||||
os.environ["KIVY_NO_ARGS"] = "1"
 | 
			
		||||
from concurrent.futures import ThreadPoolExecutor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
from queue import Queue
 | 
			
		||||
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, logger
 | 
			
		||||
from MultiServer import mark_raw
 | 
			
		||||
 | 
			
		||||
import Utils
 | 
			
		||||
import random
 | 
			
		||||
from NetUtils import RawJSONtoTextParser, NetworkItem, ClientStatus
 | 
			
		||||
 | 
			
		||||
from worlds.factorio.Technologies import lookup_id_to_name
 | 
			
		||||
 | 
			
		||||
rcon_port = 24242
 | 
			
		||||
rcon_password = ''.join(random.choice(string.ascii_letters) for x in range(32))
 | 
			
		||||
save_name = "Archipelago"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
options = Utils.get_options()
 | 
			
		||||
executable = options["factorio_options"]["executable"]
 | 
			
		||||
bin_dir = os.path.dirname(executable)
 | 
			
		||||
if not os.path.isdir(bin_dir):
 | 
			
		||||
    raise FileNotFoundError(bin_dir)
 | 
			
		||||
if not os.path.exists(executable):
 | 
			
		||||
    if os.path.exists(executable + ".exe"):
 | 
			
		||||
        executable = executable + ".exe"
 | 
			
		||||
    else:
 | 
			
		||||
        raise FileNotFoundError(executable)
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
server_args = (save_name, "--rcon-port", rcon_port, "--rcon-password", rcon_password, *sys.argv[1:])
 | 
			
		||||
 | 
			
		||||
threadpool = ThreadPoolExecutor(10)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorioCommandProcessor(ClientCommandProcessor):
 | 
			
		||||
    @mark_raw
 | 
			
		||||
    def _cmd_factorio(self, text: str) -> bool:
 | 
			
		||||
        """Send the following command to the bound Factorio Server."""
 | 
			
		||||
        if self.ctx.rcon_client:
 | 
			
		||||
            result = self.ctx.rcon_client.send_command(text)
 | 
			
		||||
            if result:
 | 
			
		||||
                self.output(result)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _cmd_connect(self, address: str = "") -> bool:
 | 
			
		||||
        """Connect to a MultiWorld Server"""
 | 
			
		||||
        if not self.ctx.auth:
 | 
			
		||||
            self.output("Cannot connect to a server with unknown own identity, bridge to Factorio first.")
 | 
			
		||||
        return super(FactorioCommandProcessor, self)._cmd_connect(address)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorioContext(CommonContext):
 | 
			
		||||
    command_processor = FactorioCommandProcessor
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(FactorioContext, self).__init__(*args, **kwargs)
 | 
			
		||||
        self.send_index = 0
 | 
			
		||||
        self.rcon_client = None
 | 
			
		||||
        self.raw_json_text_parser = RawJSONtoTextParser(self)
 | 
			
		||||
 | 
			
		||||
    async def server_auth(self, password_requested):
 | 
			
		||||
        if password_requested and not self.password:
 | 
			
		||||
            await super(FactorioContext, self).server_auth(password_requested)
 | 
			
		||||
 | 
			
		||||
        await self.send_msgs([{"cmd": 'Connect',
 | 
			
		||||
                               'password': self.password, 'name': self.auth, 'version': Utils._version_tuple,
 | 
			
		||||
                               'tags': ['AP'],
 | 
			
		||||
                               'uuid': Utils.get_unique_identifier(), 'game': "Factorio"
 | 
			
		||||
                               }])
 | 
			
		||||
 | 
			
		||||
    def on_print(self, args: dict):
 | 
			
		||||
        logger.info(args["text"])
 | 
			
		||||
        if self.rcon_client:
 | 
			
		||||
            cleaned_text = args['text'].replace('"', '')
 | 
			
		||||
            self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
 | 
			
		||||
 | 
			
		||||
    def on_print_json(self, args: dict):
 | 
			
		||||
        if not self.found_items and args.get("type", None) == "ItemSend" and args["receiving"] == args["sending"]:
 | 
			
		||||
            pass  # don't want info on other player's local pickups.
 | 
			
		||||
        text = self.raw_json_text_parser(args["data"])
 | 
			
		||||
        logger.info(text)
 | 
			
		||||
        if self.rcon_client:
 | 
			
		||||
            cleaned_text = text.replace('"', '')
 | 
			
		||||
            self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")
 | 
			
		||||
 | 
			
		||||
async def game_watcher(ctx: FactorioContext, bridge_file: str):
 | 
			
		||||
    bridge_logger = logging.getLogger("FactorioWatcher")
 | 
			
		||||
    from worlds.factorio.Technologies import lookup_id_to_name
 | 
			
		||||
    bridge_counter = 0
 | 
			
		||||
    try:
 | 
			
		||||
        while not ctx.exit_event.is_set():
 | 
			
		||||
            if os.path.exists(bridge_file):
 | 
			
		||||
                bridge_logger.info("Found Factorio Bridge file.")
 | 
			
		||||
                while not ctx.exit_event.is_set():
 | 
			
		||||
                    with open(bridge_file) as f:
 | 
			
		||||
                        data = json.load(f)
 | 
			
		||||
                        research_data = data["research_done"]
 | 
			
		||||
                        research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
 | 
			
		||||
                        victory = data["victory"]
 | 
			
		||||
                        ctx.auth = data["slot_name"]
 | 
			
		||||
                        ctx.seed_name = data["seed_name"]
 | 
			
		||||
 | 
			
		||||
                    if not ctx.finished_game and victory:
 | 
			
		||||
                        await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
 | 
			
		||||
                        ctx.finished_game = True
 | 
			
		||||
 | 
			
		||||
                    if ctx.locations_checked != research_data:
 | 
			
		||||
                        bridge_logger.info(f"New researches done: "
 | 
			
		||||
                                           f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
 | 
			
		||||
                        ctx.locations_checked = research_data
 | 
			
		||||
                        await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
 | 
			
		||||
                    await asyncio.sleep(1)
 | 
			
		||||
            else:
 | 
			
		||||
                bridge_counter += 1
 | 
			
		||||
                if bridge_counter >= 60:
 | 
			
		||||
                    bridge_logger.info(
 | 
			
		||||
                        "Did not find Factorio Bridge file, "
 | 
			
		||||
                        "waiting for mod to run, which requires the server to run, "
 | 
			
		||||
                        "which requires a player to be connected.")
 | 
			
		||||
                    bridge_counter = 0
 | 
			
		||||
                await asyncio.sleep(1)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        logging.exception(e)
 | 
			
		||||
        logging.error("Aborted Factorio Server Bridge")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stream_factorio_output(pipe, queue):
 | 
			
		||||
    def queuer():
 | 
			
		||||
        while 1:
 | 
			
		||||
            text = pipe.readline().strip()
 | 
			
		||||
            if text:
 | 
			
		||||
                queue.put_nowait(text)
 | 
			
		||||
 | 
			
		||||
    from threading import Thread
 | 
			
		||||
 | 
			
		||||
    thread = Thread(target=queuer, name="Factorio Output Queue", daemon=True)
 | 
			
		||||
    thread.start()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def factorio_server_watcher(ctx: FactorioContext):
 | 
			
		||||
    import subprocess
 | 
			
		||||
    import factorio_rcon
 | 
			
		||||
    factorio_server_logger = logging.getLogger("FactorioServer")
 | 
			
		||||
    factorio_process = subprocess.Popen((executable, "--start-server", *(str(elem) for elem in server_args)),
 | 
			
		||||
                                        stderr=subprocess.PIPE,
 | 
			
		||||
                                        stdout=subprocess.PIPE,
 | 
			
		||||
                                        stdin=subprocess.DEVNULL,
 | 
			
		||||
                                        encoding="utf-8")
 | 
			
		||||
    factorio_server_logger.info("Started Factorio Server")
 | 
			
		||||
    factorio_queue = Queue()
 | 
			
		||||
    stream_factorio_output(factorio_process.stdout, factorio_queue)
 | 
			
		||||
    stream_factorio_output(factorio_process.stderr, factorio_queue)
 | 
			
		||||
    script_folder = None
 | 
			
		||||
    progression_watcher = None
 | 
			
		||||
    try:
 | 
			
		||||
        while not ctx.exit_event.is_set():
 | 
			
		||||
            while not factorio_queue.empty():
 | 
			
		||||
                msg = factorio_queue.get()
 | 
			
		||||
                factorio_server_logger.info(msg)
 | 
			
		||||
                if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg:
 | 
			
		||||
                    ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
 | 
			
		||||
                    # trigger lua interface confirmation
 | 
			
		||||
                    ctx.rcon_client.send_command("/sc game.print('Starting Archipelago Bridge')")
 | 
			
		||||
                    ctx.rcon_client.send_command("/sc game.print('Starting Archipelago Bridge')")
 | 
			
		||||
                    ctx.rcon_client.send_command("/ap-sync")
 | 
			
		||||
                if not script_folder and "Write data path:" in msg:
 | 
			
		||||
                    script_folder = msg.split("Write data path: ", 1)[1].split("[", 1)[0].strip()
 | 
			
		||||
                    bridge_file = os.path.join(script_folder, "script-output", "ap_bridge.json")
 | 
			
		||||
                    if os.path.exists(bridge_file):
 | 
			
		||||
                        os.remove(bridge_file)
 | 
			
		||||
                    logging.info(f"Bridge File Path: {bridge_file}")
 | 
			
		||||
                    progression_watcher= asyncio.create_task(
 | 
			
		||||
                        game_watcher(ctx, bridge_file), name="FactorioProgressionWatcher")
 | 
			
		||||
            if ctx.rcon_client:
 | 
			
		||||
                while ctx.send_index < len(ctx.items_received):
 | 
			
		||||
                    transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
 | 
			
		||||
                    item_id = transfer_item.item
 | 
			
		||||
                    player_name = ctx.player_names[transfer_item.player]
 | 
			
		||||
                    if item_id not in lookup_id_to_name:
 | 
			
		||||
                        logging.error(f"Cannot send unknown item ID: {item_id}")
 | 
			
		||||
                    else:
 | 
			
		||||
                        item_name = lookup_id_to_name[item_id]
 | 
			
		||||
                        factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
 | 
			
		||||
                        ctx.rcon_client.send_command(f'/ap-get-technology {item_name} {player_name}')
 | 
			
		||||
                    ctx.send_index += 1
 | 
			
		||||
            await asyncio.sleep(1)
 | 
			
		||||
        factorio_process.terminate()
 | 
			
		||||
        await progression_watcher
 | 
			
		||||
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        logging.exception(e)
 | 
			
		||||
        logging.error("Aborted Factorio Server Bridge")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def main():
 | 
			
		||||
    ctx = FactorioContext(None, None, True)
 | 
			
		||||
 | 
			
		||||
    ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
 | 
			
		||||
    factorio_server_task = asyncio.create_task(factorio_server_watcher(ctx), name="FactorioServer")
 | 
			
		||||
    ui_app = FactorioManager(ctx)
 | 
			
		||||
    ui_task = asyncio.create_task(ui_app.async_run(), name="UI")
 | 
			
		||||
 | 
			
		||||
    await ctx.exit_event.wait() # wait for signal to exit application
 | 
			
		||||
    ui_app.stop()
 | 
			
		||||
    ctx.server_address = None
 | 
			
		||||
    ctx.snes_reconnect_address = None
 | 
			
		||||
    # allow tasks to quit
 | 
			
		||||
    await ui_task
 | 
			
		||||
    await factorio_server_task
 | 
			
		||||
    await ctx.server_task
 | 
			
		||||
 | 
			
		||||
    if ctx.server is not None and not ctx.server.socket.closed:
 | 
			
		||||
        await ctx.server.socket.close()
 | 
			
		||||
    if ctx.server_task is not None:
 | 
			
		||||
        await ctx.server_task
 | 
			
		||||
 | 
			
		||||
    while ctx.input_requests > 0: # clear queue for shutdown
 | 
			
		||||
        ctx.input_queue.put_nowait(None)
 | 
			
		||||
        ctx.input_requests -= 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from kivy.app import App
 | 
			
		||||
from kivy.uix.label import Label
 | 
			
		||||
from kivy.uix.gridlayout import GridLayout
 | 
			
		||||
from kivy.uix.textinput import TextInput
 | 
			
		||||
from kivy.uix.recycleview import RecycleView
 | 
			
		||||
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
 | 
			
		||||
from kivy.lang import Builder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorioManager(App):
 | 
			
		||||
    def __init__(self, ctx):
 | 
			
		||||
        super(FactorioManager, self).__init__()
 | 
			
		||||
        self.ctx = ctx
 | 
			
		||||
        self.commandprocessor = ctx.command_processor(ctx)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def build(self):
 | 
			
		||||
        self.grid = GridLayout()
 | 
			
		||||
        self.grid.cols = 1
 | 
			
		||||
        self.tabs = TabbedPanel()
 | 
			
		||||
        self.tabs.default_tab_text = "All"
 | 
			
		||||
        self.title = "Archipelago Factorio Client"
 | 
			
		||||
        pairs = [
 | 
			
		||||
            ("Client", "Archipelago"),
 | 
			
		||||
            ("FactorioServer", "Factorio Server Log"),
 | 
			
		||||
            ("FactorioWatcher", "Bridge File Log"),
 | 
			
		||||
        ]
 | 
			
		||||
        self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name) for logger_name, name in pairs))
 | 
			
		||||
        for logger_name, display_name in pairs:
 | 
			
		||||
            bridge_logger = logging.getLogger(logger_name)
 | 
			
		||||
            panel = TabbedPanelHeader(text=display_name)
 | 
			
		||||
            self.tabs.add_widget(panel)
 | 
			
		||||
            panel.content = UILog(bridge_logger)
 | 
			
		||||
        self.grid.add_widget(self.tabs)
 | 
			
		||||
        textinput = TextInput(size_hint_y=None, height=30, multiline=False)
 | 
			
		||||
        textinput.bind(on_text_validate=self.on_message)
 | 
			
		||||
        self.grid.add_widget(textinput)
 | 
			
		||||
        self.commandprocessor("/help")
 | 
			
		||||
        return self.grid
 | 
			
		||||
 | 
			
		||||
    def on_stop(self):
 | 
			
		||||
        self.ctx.exit_event.set()
 | 
			
		||||
 | 
			
		||||
    def on_message(self, textinput: TextInput):
 | 
			
		||||
        try:
 | 
			
		||||
            input_text = textinput.text.strip()
 | 
			
		||||
            textinput.text = ""
 | 
			
		||||
 | 
			
		||||
            if self.ctx.input_requests > 0:
 | 
			
		||||
                self.ctx.input_requests -= 1
 | 
			
		||||
                self.ctx.input_queue.put_nowait(input_text)
 | 
			
		||||
            elif input_text:
 | 
			
		||||
                self.commandprocessor(input_text)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.exception(e)
 | 
			
		||||
 | 
			
		||||
class LogtoUI(logging.Handler):
 | 
			
		||||
    def __init__(self, on_log):
 | 
			
		||||
        super(LogtoUI, self).__init__(logging.DEBUG)
 | 
			
		||||
        self.on_log = on_log
 | 
			
		||||
 | 
			
		||||
    def handle(self, record: logging.LogRecord) -> None:
 | 
			
		||||
        self.on_log(record)
 | 
			
		||||
 | 
			
		||||
Builder.load_string('''
 | 
			
		||||
<TabbedPanel>
 | 
			
		||||
    tab_width: 200
 | 
			
		||||
<Row@Label>:
 | 
			
		||||
    canvas.before:
 | 
			
		||||
        Color:
 | 
			
		||||
            rgba: 0.2, 0.2, 0.2, 1
 | 
			
		||||
        Rectangle:
 | 
			
		||||
            size: self.size
 | 
			
		||||
            pos: self.pos
 | 
			
		||||
    text_size: self.width, None
 | 
			
		||||
    size_hint_y: None
 | 
			
		||||
    height: self.texture_size[1]
 | 
			
		||||
    font_size: dp(20)
 | 
			
		||||
<UILog>:
 | 
			
		||||
    viewclass: 'Row'
 | 
			
		||||
    scroll_y: 0
 | 
			
		||||
    effect_cls: "ScrollEffect"
 | 
			
		||||
    RecycleBoxLayout:
 | 
			
		||||
        default_size: None, dp(20)
 | 
			
		||||
        default_size_hint: 1, None
 | 
			
		||||
        size_hint_y: None
 | 
			
		||||
        height: self.minimum_height
 | 
			
		||||
        orientation: 'vertical'
 | 
			
		||||
        spacing: dp(3)
 | 
			
		||||
''')
 | 
			
		||||
 | 
			
		||||
class UILog(RecycleView):
 | 
			
		||||
    cols = 1
 | 
			
		||||
    def __init__(self, *loggers_to_handle, **kwargs):
 | 
			
		||||
        super(UILog, self).__init__(**kwargs)
 | 
			
		||||
        self.data = []
 | 
			
		||||
        for logger in loggers_to_handle:
 | 
			
		||||
            logger.addHandler(LogtoUI(self.on_log))
 | 
			
		||||
 | 
			
		||||
    def on_log(self, record: logging.LogRecord) -> None:
 | 
			
		||||
        self.data.append({"text": record.getMessage()})
 | 
			
		||||
 | 
			
		||||
    def update_text_width(self, *_):
 | 
			
		||||
        self.message.text_size = (self.message.width * 0.9, None)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    loop = asyncio.get_event_loop()
 | 
			
		||||
    loop.run_until_complete(main())
 | 
			
		||||
    loop.close()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,138 @@
 | 
			
		|||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import sys
 | 
			
		||||
import sysconfig
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import cx_Freeze
 | 
			
		||||
 | 
			
		||||
is_64bits = sys.maxsize > 2 ** 32
 | 
			
		||||
 | 
			
		||||
folder = "exe.{platform}-{version}".format(platform=sysconfig.get_platform(),
 | 
			
		||||
                                           version=sysconfig.get_python_version())
 | 
			
		||||
buildfolder = Path("build_factorio", folder)
 | 
			
		||||
sbuildfolder = str(buildfolder)
 | 
			
		||||
libfolder = Path(buildfolder, "lib")
 | 
			
		||||
library = Path(libfolder, "library.zip")
 | 
			
		||||
print("Outputting to: " + sbuildfolder)
 | 
			
		||||
 | 
			
		||||
icon = "icon.ico"
 | 
			
		||||
 | 
			
		||||
if os.path.exists("X:/pw.txt"):
 | 
			
		||||
    print("Using signtool")
 | 
			
		||||
    with open("X:/pw.txt") as f:
 | 
			
		||||
        pw = f.read()
 | 
			
		||||
    signtool = r'signtool sign /f X:/_SITS_Zertifikat_.pfx /p ' + pw + r' /fd sha256 /tr http://timestamp.digicert.com/ '
 | 
			
		||||
else:
 | 
			
		||||
    signtool = None
 | 
			
		||||
 | 
			
		||||
from hashlib import sha3_512
 | 
			
		||||
import base64
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _threaded_hash(filepath):
 | 
			
		||||
    hasher = sha3_512()
 | 
			
		||||
    hasher.update(open(filepath, "rb").read())
 | 
			
		||||
    return base64.b85encode(hasher.digest()).decode()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
os.makedirs(buildfolder, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def manifest_creation():
 | 
			
		||||
    hashes = {}
 | 
			
		||||
    manifestpath = os.path.join(buildfolder, "manifest.json")
 | 
			
		||||
    from concurrent.futures import ThreadPoolExecutor
 | 
			
		||||
    pool = ThreadPoolExecutor()
 | 
			
		||||
    for dirpath, dirnames, filenames in os.walk(buildfolder):
 | 
			
		||||
        for filename in filenames:
 | 
			
		||||
            path = os.path.join(dirpath, filename)
 | 
			
		||||
            hashes[os.path.relpath(path, start=buildfolder)] = pool.submit(_threaded_hash, path)
 | 
			
		||||
    import json
 | 
			
		||||
    from Utils import _version_tuple
 | 
			
		||||
    manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds"),
 | 
			
		||||
                "hashes": {path: hash.result() for path, hash in hashes.items()},
 | 
			
		||||
                "version": _version_tuple}
 | 
			
		||||
    json.dump(manifest, open(manifestpath, "wt"), indent=4)
 | 
			
		||||
    print("Created Manifest")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
scripts = {"FactorioClient.py": "ArchipelagoConsoleFactorioClient"}
 | 
			
		||||
 | 
			
		||||
exes = []
 | 
			
		||||
 | 
			
		||||
for script, scriptname in scripts.items():
 | 
			
		||||
    exes.append(cx_Freeze.Executable(
 | 
			
		||||
        script=script,
 | 
			
		||||
        target_name=scriptname + ("" if sys.platform == "linux" else ".exe"),
 | 
			
		||||
        icon=icon,
 | 
			
		||||
    ))
 | 
			
		||||
exes.append(cx_Freeze.Executable(
 | 
			
		||||
    script="FactorioClientGUI.py",
 | 
			
		||||
    target_name="ArchipelagoGraphicalFactorioClient" + ("" if sys.platform == "linux" else ".exe"),
 | 
			
		||||
    icon=icon,
 | 
			
		||||
    base="Win32GUI"
 | 
			
		||||
))
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
buildtime = datetime.datetime.utcnow()
 | 
			
		||||
 | 
			
		||||
cx_Freeze.setup(
 | 
			
		||||
    name="Archipelago",
 | 
			
		||||
    version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}",
 | 
			
		||||
    description="Archipelago",
 | 
			
		||||
    executables=exes,
 | 
			
		||||
    options={
 | 
			
		||||
        "build_exe": {
 | 
			
		||||
            "packages": ["websockets", "kivy"],
 | 
			
		||||
            "includes": [],
 | 
			
		||||
            "excludes": ["numpy", "Cython", "PySide2", "PIL",
 | 
			
		||||
                         "pandas"],
 | 
			
		||||
            "zip_include_packages": ["*"],
 | 
			
		||||
            "zip_exclude_packages": ["kivy"],
 | 
			
		||||
            "include_files": [],
 | 
			
		||||
            "include_msvcr": True,
 | 
			
		||||
            "replace_paths": [("*", "")],
 | 
			
		||||
            "optimize": 2,
 | 
			
		||||
            "build_exe": buildfolder
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def installfile(path, keep_content=False):
 | 
			
		||||
    lbuildfolder = buildfolder
 | 
			
		||||
    print('copying', path, '->', lbuildfolder)
 | 
			
		||||
    if path.is_dir():
 | 
			
		||||
        lbuildfolder /= path.name
 | 
			
		||||
        if lbuildfolder.is_dir() and not keep_content:
 | 
			
		||||
            shutil.rmtree(lbuildfolder)
 | 
			
		||||
        shutil.copytree(path, lbuildfolder, dirs_exist_ok=True)
 | 
			
		||||
    elif path.is_file():
 | 
			
		||||
        shutil.copy(path, lbuildfolder)
 | 
			
		||||
    else:
 | 
			
		||||
        print('Warning,', path, 'not found')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
#define sourcepath "build_factorio\exe.win-amd64-3.8\"
 | 
			
		||||
#define MyAppName "Archipelago Factorio Client"
 | 
			
		||||
#define MyAppExeName "ArchipelagoGraphicalFactorioClient.exe"
 | 
			
		||||
#define MyAppIcon "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.28.29325') < 0);
 | 
			
		||||
  end
 | 
			
		||||
  else
 | 
			
		||||
  begin
 | 
			
		||||
    // Not even an old version installed
 | 
			
		||||
    Log('VC Redist x64 is not already installed');
 | 
			
		||||
    Result := True;
 | 
			
		||||
  end;
 | 
			
		||||
end;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue