From 20b72369d8e52234ed7acdb6c2ec73849b189a31 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 4 Apr 2021 03:18:19 +0200 Subject: [PATCH] allow basic WebHost functionality to work --- MultiServer.py | 3 +- Utils.py | 8 +++-- WebHostLib/customserver.py | 8 ++--- WebHostLib/tracker.py | 67 ++++++++++++++++++++------------------ WebHostLib/upload.py | 11 +++++-- 5 files changed, 57 insertions(+), 40 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 912a0272..f170ed43 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -119,7 +119,8 @@ class Context(Node): self._load(self._decompress(data), use_embedded_server_options) self.data_filename = multidatapath - def _decompress(self, data: bytes) -> dict: + @staticmethod + def _decompress(data: bytes) -> dict: format_version = data[0] if format_version != 1: raise Exception("Incompatible multidata.") diff --git a/Utils.py b/Utils.py index 0660e30b..3aba13b6 100644 --- a/Utils.py +++ b/Utils.py @@ -84,9 +84,13 @@ def local_path(*path): # cx_Freeze local_path.cached_path = os.path.dirname(os.path.abspath(sys.argv[0])) else: - # we are running in a normal Python environment import __main__ - local_path.cached_path = os.path.dirname(os.path.abspath(__main__.__file__)) + if hasattr(__main__, "__file__"): + # we are running in a normal Python environment + local_path.cached_path = os.path.dirname(os.path.abspath(__main__.__file__)) + else: + # pray + local_path.cached_path = os.path.abspath(".") return os.path.join(local_path.cached_path, *path) diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index d8875161..080f396e 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -9,13 +9,13 @@ import socket import threading import time import random -import zlib +import pickle from .models import * from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor -from Utils import get_public_ipv4, get_public_ipv6, parse_yaml +from Utils import get_public_ipv4, get_public_ipv6, restricted_loads class CustomClientMessageProcessor(ClientMessageProcessor): @@ -81,7 +81,7 @@ class WebHostContext(Context): def init_save(self, enabled: bool = True): self.saving = enabled if self.saving: - existing_savegame = Room.get(id=self.room_id).multisave + existing_savegame = restricted_loads(Room.get(id=self.room_id).multisave) if existing_savegame: self.set_save(existing_savegame) self._start_async_saving() @@ -90,7 +90,7 @@ class WebHostContext(Context): @db_session def _save(self, exit_save:bool = False) -> bool: room = Room.get(id=self.room_id) - room.multisave = self.get_save() + room.multisave = pickle.dumps(self.get_save()) # saving only occurs on activity, so we can "abuse" this information to mark this as last_activity if not exit_save: # we don't want to count a shutdown as activity, which would restart the server again room.last_activity = datetime.utcnow() diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index e97ed8f3..3770e3d5 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -8,6 +8,7 @@ from uuid import UUID from worlds.alttp import Items, Regions from WebHostLib import app, cache, Room from NetUtils import Hint +from Utils import restricted_loads def get_id(item_name): @@ -253,7 +254,7 @@ for item_name, data in Items.item_table.items(): big_key_ids[area] = data[2] ids_big_key[data[2]] = area -from MultiServer import get_item_name_from_id +from MultiServer import get_item_name_from_id, Context def attribute_item(inventory, team, recipient, item): @@ -295,9 +296,9 @@ def get_static_room_data(room: Room): result = _multidata_cache.get(room.seed.id, None) if result: return result - multidata = room.seed.multidata + multidata = Context._decompress(room.seed.multidata) # in > 100 players this can take a bit of time and is the main reason for the cache - locations = {tuple(k): tuple(v) for k, v in multidata['locations']} + locations = multidata['locations'] names = multidata["names"] seed_checks_in_area = checks_in_area.copy() @@ -308,30 +309,24 @@ def get_static_room_data(room: Room): for area, checks in key_only_locations.items(): seed_checks_in_area[area] += len(checks) seed_checks_in_area["Total"] = 249 - if "checks_in_area" not in multidata: - player_checks_in_area = {playernumber: (seed_checks_in_area if use_door_tracker and - (0x140031, playernumber) in locations else checks_in_area) - for playernumber in range(1, len(names[0]) + 1)} - player_location_to_area = {playernumber: location_to_area - for playernumber in range(1, len(names[0]) + 1)} - else: - player_checks_in_area = {playernumber: {areaname: len(multidata["checks_in_area"][f'{playernumber}'][areaname]) - if areaname != "Total" else multidata["checks_in_area"][f'{playernumber}']["Total"] - for areaname in ordered_areas} - for playernumber in range(1, len(names[0]) + 1)} - player_location_to_area = {playernumber: get_location_table(multidata["checks_in_area"][f'{playernumber}']) - for playernumber in range(1, len(names[0]) + 1)} + player_checks_in_area = {playernumber: {areaname: len(multidata["checks_in_area"][playernumber][areaname]) + if areaname != "Total" else multidata["checks_in_area"][playernumber]["Total"] + for areaname in ordered_areas} + for playernumber in range(1, len(names[0]) + 1)} + player_location_to_area = {playernumber: get_location_table(multidata["checks_in_area"][playernumber]) + for playernumber in range(1, len(names[0]) + 1)} player_big_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} player_small_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} - for _, (item_id, item_player) in multidata["locations"]: + for _, (item_id, item_player) in locations.items(): if item_id in ids_big_key: player_big_key_locations[item_player].add(ids_big_key[item_id]) if item_id in ids_small_key: player_small_key_locations[item_player].add(ids_small_key[item_id]) - result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area, player_big_key_locations, player_small_key_locations + result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area, \ + player_big_key_locations, player_small_key_locations, multidata["precollected_items"] _multidata_cache[room.seed.id] = result return result @@ -348,7 +343,7 @@ def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int): abort(404) # Collect seed information and pare it down to a single player - locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, player_big_key_locations, player_small_key_locations = get_static_room_data(room) + locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, player_big_key_locations, player_small_key_locations, precollected_items = get_static_room_data(room) player_name = names[tracked_team][tracked_player - 1] seed_checks_in_area = seed_checks_in_area[tracked_player] location_to_area = player_location_to_area[tracked_player] @@ -356,13 +351,18 @@ def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int): checks_done = {loc_name: 0 for loc_name in default_locations} # Add starting items to inventory - starting_items = room.seed.multidata.get("precollected_items", None)[tracked_player - 1] + starting_items = precollected_items[tracked_player - 1] if starting_items: for item_id in starting_items: attribute_item_solo(inventory, item_id) + if room.multisave: + multisave = restricted_loads(room.multisave) + else: + multisave = {} + # Add items to player inventory - for (ms_team, ms_player), locations_checked in room.multisave.get("location_checks", {}): + for (ms_team, ms_player), locations_checked in multisave.get("location_checks", {}): # logging.info(f"{ms_team}, {ms_player}, {locations_checked}") # Skip teams and players not matching the request @@ -380,7 +380,7 @@ def getPlayerTracker(tracker: UUID, tracked_team: int, tracked_player: int): checks_done["Total"] += 1 # Note the presence of the triforce item - for (ms_team, ms_player), game_state in room.multisave.get("client_game_state", []): + for (ms_team, ms_player), game_state in multisave.get("client_game_state", []): # Skip teams and players not matching the request if ms_team != tracked_team or ms_player != tracked_player: continue @@ -484,7 +484,8 @@ def getTracker(tracker: UUID): room = Room.get(tracker=tracker) if not room: abort(404) - locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, player_big_key_locations, player_small_key_locations = get_static_room_data(room) + locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, player_big_key_locations, \ + player_small_key_locations, precollected_items = get_static_room_data(room) inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in range(1, len(team) + 1)} for teamnumber, team in enumerate(names)} @@ -492,14 +493,18 @@ def getTracker(tracker: UUID): checks_done = {teamnumber: {playernumber: {loc_name: 0 for loc_name in default_locations} for playernumber in range(1, len(team) + 1)} for teamnumber, team in enumerate(names)} - precollected_items = room.seed.multidata.get("precollected_items", None) + hints = {team: set() for team in range(len(names))} - if "hints" in room.multisave: - for key, hintdata in room.multisave["hints"]: + if room.multisave: + multisave = restricted_loads(room.multisave) + else: + multisave = {} + if "hints" in multisave: + for key, hintdata in multisave["hints"]: for hint in hintdata: hints[key[0]].add(Hint(*hint)) - for (team, player), locations_checked in room.multisave.get("location_checks", {}): + for (team, player), locations_checked in multisave.get("location_checks", {}): if precollected_items: precollected = precollected_items[player - 1] for item_id in precollected: @@ -513,7 +518,7 @@ def getTracker(tracker: UUID): checks_done[team][player][player_location_to_area[player][location]] += 1 checks_done[team][player]["Total"] += 1 - for (team, player), game_state in room.multisave.get("client_game_state", []): + for (team, player), game_state in multisave.get("client_game_state", []): if game_state: inventory[team][player][106] = 1 # Triforce @@ -525,7 +530,7 @@ def getTracker(tracker: UUID): activity_timers = {} now = datetime.datetime.utcnow() - for (team, player), timestamp in room.multisave.get("client_activity_timers", []): + for (team, player), timestamp in multisave.get("client_activity_timers", []): activity_timers[team, player] = now - datetime.datetime.utcfromtimestamp(timestamp) player_names = {} @@ -533,12 +538,12 @@ def getTracker(tracker: UUID): for player, name in enumerate(names, 1): player_names[(team, player)] = name long_player_names = player_names.copy() - for (team, player), alias in room.multisave.get("name_aliases", []): + for (team, player), alias in multisave.get("name_aliases", []): player_names[(team, player)] = alias long_player_names[(team, player)] = f"{alias} ({long_player_names[(team, player)]})" video = {} - for (team, player), data in room.multisave.get("video", []): + for (team, player), data in multisave.get("video", []): video[(team, player)] = data return render_template("tracker.html", inventory=inventory, get_item_name_from_id=get_item_name_from_id, diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index a5e5d3d5..8483a74f 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -2,6 +2,7 @@ import json import zlib import zipfile import logging +import MultiServer from flask import request, flash, redirect, url_for, session, render_template from pony.orm import commit, select @@ -46,9 +47,12 @@ def uploads(): spoiler = zfile.open(file, "r").read().decode("utf-8-sig") elif file.filename.endswith(".archipelago"): try: - multidata = json.loads(zlib.decompress(zfile.open(file).read()).decode("utf-8-sig")) + multidata = zfile.open(file).read() + MultiServer.Context._decompress(multidata) except: flash("Could not load multidata. File may be corrupted or incompatible.") + else: + multidata = zfile.open(file).read() if multidata: commit() # commit patches seed = Seed(multidata=multidata, spoiler=spoiler, patches=patches, owner=session["_id"]) @@ -61,10 +65,13 @@ def uploads(): flash("No multidata was found in the zip file, which is required.") else: try: - multidata = json.loads(zlib.decompress(file.read()).decode("utf-8-sig")) + multidata = file.read() + MultiServer.Context._decompress(multidata) except: flash("Could not load multidata. File may be corrupted or incompatible.") + raise else: + logging.info(multidata) seed = Seed(multidata=multidata, owner=session["_id"]) commit() # place into DB and generate ids return redirect(url_for("viewSeed", seed=seed.id))