squashed commit of many breaking changes
Dropping Support for Python 3.7; adding support for Python 3.9
This commit is contained in:
parent
add0762114
commit
4f8c737eec
|
@ -11,7 +11,7 @@ def adjust(args):
|
|||
logger = logging.getLogger('Adjuster')
|
||||
logger.info('Patching ROM.')
|
||||
|
||||
if os.path.splitext(args.rom)[-1].lower() == '.bmbp':
|
||||
if os.path.splitext(args.rom)[-1].lower() == '.apbp':
|
||||
import Patch
|
||||
meta, args.rom = Patch.create_rom_file(args.rom)
|
||||
|
||||
|
|
2
Gui.py
2
Gui.py
|
@ -655,7 +655,7 @@ def guiMain(args=None):
|
|||
romEntry2 = Entry(romDialogFrame2, textvariable=romVar2)
|
||||
|
||||
def RomSelect2():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".bmbp")), ("All Files", "*")])
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")])
|
||||
romVar2.set(rom)
|
||||
romSelectButton2 = Button(romDialogFrame2, text='Select Rom', command=RomSelect2)
|
||||
|
||||
|
|
39
Main.py
39
Main.py
|
@ -1,7 +1,6 @@
|
|||
from collections import OrderedDict
|
||||
import copy
|
||||
from itertools import zip_longest
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
|
@ -170,7 +169,7 @@ def main(args, seed=None):
|
|||
|
||||
logger.info('Patching ROM.')
|
||||
|
||||
outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed)
|
||||
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
||||
|
||||
rom_names = []
|
||||
|
||||
|
@ -258,7 +257,7 @@ def main(args, seed=None):
|
|||
rom.write_to_file(rompath, hide_enemizer=True)
|
||||
if args.create_diff:
|
||||
Patch.create_patch_file(rompath)
|
||||
return player, team, bytes(rom.name).decode()
|
||||
return player, team, bytes(rom.name)
|
||||
|
||||
pool = concurrent.futures.ThreadPoolExecutor()
|
||||
multidata_task = None
|
||||
|
@ -293,26 +292,26 @@ def main(args, seed=None):
|
|||
precollected_items[item.player - 1].append(item.code)
|
||||
|
||||
def write_multidata(roms):
|
||||
import base64
|
||||
import pickle
|
||||
for future in roms:
|
||||
rom_name = future.result()
|
||||
rom_names.append(rom_name)
|
||||
multidata = zlib.compress(json.dumps({"names": parsed_names,
|
||||
# backwards compat for < 2.4.1
|
||||
"roms": [(slot, team, list(name.encode()))
|
||||
for (slot, team, name) in rom_names],
|
||||
"rom_strings": rom_names,
|
||||
"remote_items": [player for player in range(1, world.players + 1) if
|
||||
world.remote_items[player]],
|
||||
"locations": [((location.address, location.player),
|
||||
(location.item.code, location.item.player))
|
||||
for location in world.get_filled_locations() if
|
||||
type(location.address) is int],
|
||||
"server_options": get_options()["server_options"],
|
||||
"er_hint_data": er_hint_data,
|
||||
"precollected_items": precollected_items,
|
||||
"version": _version_tuple,
|
||||
"tags": ["ER"]
|
||||
}).encode("utf-8"), 9)
|
||||
multidata = zlib.compress(pickle.dumps({"names": parsed_names,
|
||||
"roms": {base64.b64encode(rom_name).decode(): (team, slot) for slot, team, rom_name in rom_names},
|
||||
"remote_items": {player for player in range(1, world.players + 1) if
|
||||
world.remote_items[player]},
|
||||
"locations": {
|
||||
(location.address, location.player) :
|
||||
(location.item.code, location.item.player)
|
||||
for location in world.get_filled_locations() if
|
||||
type(location.address) is int},
|
||||
"server_options": get_options()["server_options"],
|
||||
"er_hint_data": er_hint_data,
|
||||
"precollected_items": precollected_items,
|
||||
"version": _version_tuple,
|
||||
"tags": ["AP"]
|
||||
}), 9)
|
||||
|
||||
with open(output_path('%s.multidata' % outfilebase), 'wb') as f:
|
||||
f.write(multidata)
|
||||
|
|
|
@ -3,6 +3,10 @@ import sys
|
|||
import subprocess
|
||||
import importlib
|
||||
|
||||
|
||||
if sys.version_info < (3, 8, 6):
|
||||
raise RuntimeError("Incompatible Python Version. 3.8.7+ is supported.")
|
||||
|
||||
update_ran = hasattr(sys, "frozen") and getattr(sys, "frozen") # don't run update if environment is frozen/compiled
|
||||
|
||||
|
||||
|
|
122
MultiClient.py
122
MultiClient.py
|
@ -1,6 +1,5 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import urllib.parse
|
||||
import atexit
|
||||
|
@ -13,6 +12,8 @@ import sys
|
|||
import typing
|
||||
import os
|
||||
import subprocess
|
||||
import base64
|
||||
from json import loads, dumps
|
||||
|
||||
from random import randrange
|
||||
|
||||
|
@ -113,7 +114,7 @@ class Context():
|
|||
async def send_msgs(self, msgs):
|
||||
if not self.server or not self.server.socket.open or self.server.socket.closed:
|
||||
return
|
||||
await self.server.socket.send(json.dumps(msgs))
|
||||
await self.server.socket.send(dumps(msgs))
|
||||
|
||||
color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||
'magenta': 35, 'cyan': 36, 'white': 37, 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||
|
@ -427,17 +428,17 @@ async def get_snes_devices(ctx: Context):
|
|||
"Opcode": "DeviceList",
|
||||
"Space": "SNES"
|
||||
}
|
||||
await socket.send(json.dumps(DeviceList_Request))
|
||||
await socket.send(dumps(DeviceList_Request))
|
||||
|
||||
reply = json.loads(await socket.recv())
|
||||
reply = loads(await socket.recv())
|
||||
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None
|
||||
|
||||
if not devices:
|
||||
ctx.ui_node.log_info('No SNES device found. Ensure QUsb2Snes is running and connect it to the multibridge.')
|
||||
while not devices:
|
||||
await asyncio.sleep(1)
|
||||
await socket.send(json.dumps(DeviceList_Request))
|
||||
reply = json.loads(await socket.recv())
|
||||
await socket.send(dumps(DeviceList_Request))
|
||||
reply = loads(await socket.recv())
|
||||
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None
|
||||
|
||||
ctx.ui_node.send_device_list(devices)
|
||||
|
@ -481,7 +482,7 @@ async def snes_connect(ctx: Context, address):
|
|||
"Space": "SNES",
|
||||
"Operands": [device]
|
||||
}
|
||||
await ctx.snes_socket.send(json.dumps(Attach_Request))
|
||||
await ctx.snes_socket.send(dumps(Attach_Request))
|
||||
ctx.snes_state = SNES_ATTACHED
|
||||
ctx.snes_attached_device = (devices.index(device), device)
|
||||
ctx.ui_node.send_connection_status(ctx)
|
||||
|
@ -489,8 +490,8 @@ async def snes_connect(ctx: Context, address):
|
|||
if 'sd2snes' in device.lower() or (len(device) == 4 and device[:3] == 'COM'):
|
||||
ctx.ui_node.log_info("SD2SNES Detected")
|
||||
ctx.is_sd2snes = True
|
||||
await ctx.snes_socket.send(json.dumps({"Opcode" : "Info", "Space" : "SNES"}))
|
||||
reply = json.loads(await ctx.snes_socket.recv())
|
||||
await ctx.snes_socket.send(dumps({"Opcode" : "Info", "Space" : "SNES"}))
|
||||
reply = loads(await ctx.snes_socket.recv())
|
||||
if reply and 'Results' in reply:
|
||||
ctx.ui_node.log_info(reply['Results'])
|
||||
else:
|
||||
|
@ -501,7 +502,6 @@ async def snes_connect(ctx: Context, address):
|
|||
SNES_RECONNECT_DELAY = START_RECONNECT_DELAY
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if recv_task is not None:
|
||||
if not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
|
@ -576,7 +576,7 @@ async def snes_read(ctx : Context, address, size):
|
|||
"Operands" : [hex(address)[2:], hex(size)[2:]]
|
||||
}
|
||||
try:
|
||||
await ctx.snes_socket.send(json.dumps(GetAddress_Request))
|
||||
await ctx.snes_socket.send(dumps(GetAddress_Request))
|
||||
except websockets.ConnectionClosed:
|
||||
return None
|
||||
|
||||
|
@ -633,7 +633,7 @@ async def snes_write(ctx : Context, write_list):
|
|||
PutAddress_Request['Operands'] = ["2C00", hex(len(cmd)-1)[2:], "2C00", "1"]
|
||||
try:
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||
await ctx.snes_socket.send(dumps(PutAddress_Request))
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(cmd)
|
||||
except websockets.ConnectionClosed:
|
||||
|
@ -645,7 +645,7 @@ async def snes_write(ctx : Context, write_list):
|
|||
for address, data in write_list:
|
||||
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||
await ctx.snes_socket.send(dumps(PutAddress_Request))
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(data)
|
||||
except websockets.ConnectionClosed:
|
||||
|
@ -674,7 +674,7 @@ async def snes_flush_writes(ctx : Context):
|
|||
async def send_msgs(websocket, msgs):
|
||||
if not websocket or not websocket.open or websocket.closed:
|
||||
return
|
||||
await websocket.send(json.dumps(msgs))
|
||||
await websocket.send(dumps(msgs))
|
||||
|
||||
|
||||
async def server_loop(ctx: Context, address=None):
|
||||
|
@ -685,16 +685,16 @@ async def server_loop(ctx: Context, address=None):
|
|||
ctx.ui_node.log_error('Already connected')
|
||||
return
|
||||
|
||||
if address is None: # set through CLI or BMBP
|
||||
if address is None: # set through CLI or APBP
|
||||
address = ctx.server_address
|
||||
if address is None: # see if this is an old connection
|
||||
await asyncio.sleep(0.5) # wait for snes connection to succeed if possible.
|
||||
rom = ctx.rom if ctx.rom else None
|
||||
try:
|
||||
servers = cached_address = Utils.persistent_load()["servers"]
|
||||
address = servers[rom] if rom and rom in servers else servers["default"]
|
||||
except Exception as e:
|
||||
logging.debug(f"Could not find cached server address. {e}")
|
||||
|
||||
servers = Utils.persistent_load()["servers"]
|
||||
if rom in servers:
|
||||
address = servers[rom]
|
||||
cached_address = True
|
||||
|
||||
# Wait for the user to provide a multiworld server address
|
||||
if not address:
|
||||
|
@ -714,7 +714,7 @@ async def server_loop(ctx: Context, address=None):
|
|||
ctx.ui_node.send_connection_status(ctx)
|
||||
SERVER_RECONNECT_DELAY = START_RECONNECT_DELAY
|
||||
async for data in ctx.server.socket:
|
||||
for msg in json.loads(data):
|
||||
for msg in loads(data):
|
||||
cmd, args = (msg[0], msg[1]) if len(msg) > 1 else (msg, None)
|
||||
await process_server_cmd(ctx, cmd, args)
|
||||
ctx.ui_node.log_warning('Disconnected from multiworld server, type /connect to reconnect')
|
||||
|
@ -806,7 +806,6 @@ async def process_server_cmd(ctx: Context, cmd, args):
|
|||
raise Exception(
|
||||
'Invalid ROM detected, please verify that you have loaded the correct rom and reconnect your snes (/snes)')
|
||||
if 'SlotAlreadyTaken' in args:
|
||||
Utils.persistent_store("servers", "default", ctx.server_address)
|
||||
Utils.persistent_store("servers", ctx.rom, ctx.server_address)
|
||||
raise Exception('Player slot already in use for that team')
|
||||
if 'IncompatibleVersion' in args:
|
||||
|
@ -814,7 +813,6 @@ async def process_server_cmd(ctx: Context, cmd, args):
|
|||
raise Exception('Connection refused by the multiworld host')
|
||||
|
||||
elif cmd == 'Connected':
|
||||
Utils.persistent_store("servers", "default", ctx.server_address)
|
||||
Utils.persistent_store("servers", ctx.rom, ctx.server_address)
|
||||
ctx.team, ctx.slot = args[0]
|
||||
ctx.player_names = {p: n for p, n in args[1]}
|
||||
|
@ -873,12 +871,6 @@ async def process_server_cmd(ctx: Context, cmd, args):
|
|||
logging.info('%s found %s (%s)' % (player_sent, item, color(get_location_name_from_address(found.location),
|
||||
'blue_bg', 'white')))
|
||||
|
||||
elif cmd == 'Missing':
|
||||
if 'locations' in args:
|
||||
locations = json.loads(args['locations'])
|
||||
for location in locations:
|
||||
ctx.ui_node.log_info(f'Missing: {location}')
|
||||
ctx.ui_node.log_info(f'Found {len(locations)} missing location checks')
|
||||
|
||||
elif cmd == 'Hint':
|
||||
hints = [Utils.Hint(*hint) for hint in args]
|
||||
|
@ -931,7 +923,7 @@ async def server_auth(ctx: Context, password_requested):
|
|||
return
|
||||
ctx.awaiting_rom = False
|
||||
ctx.auth = ctx.rom
|
||||
auth = ctx.rom if ctx.server_version > (2, 4, 0) else list(ctx.rom.encode())
|
||||
auth = base64.b64encode(ctx.rom).decode()
|
||||
await ctx.send_msgs([['Connect', {
|
||||
'password': ctx.password, 'rom': auth, 'version': Utils._version_tuple, 'tags': get_tags(ctx),
|
||||
'uuid': Utils.get_unique_identifier()
|
||||
|
@ -1156,7 +1148,7 @@ async def game_watcher(ctx : Context):
|
|||
if rom is None or rom == bytes([0] * ROMNAME_SIZE):
|
||||
continue
|
||||
|
||||
ctx.rom = rom.decode()
|
||||
ctx.rom = rom
|
||||
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
|
||||
ctx.locations_checked = set()
|
||||
ctx.locations_scouted = set()
|
||||
|
@ -1251,45 +1243,43 @@ async def websocket_server(websocket: websockets.WebSocketServerProtocol, path,
|
|||
process_command = ClientCommandProcessor(ctx)
|
||||
try:
|
||||
async for incoming_data in websocket:
|
||||
try:
|
||||
data = json.loads(incoming_data)
|
||||
logging.debug(f"WebUIData:{data}")
|
||||
if ('type' not in data) or ('content' not in data):
|
||||
raise Exception('Invalid data received in websocket')
|
||||
data = loads(incoming_data)
|
||||
logging.debug(f"WebUIData:{data}")
|
||||
if ('type' not in data) or ('content' not in data):
|
||||
raise Exception('Invalid data received in websocket')
|
||||
|
||||
elif data['type'] == 'webStatus':
|
||||
if data['content'] == 'connections':
|
||||
ctx.ui_node.send_connection_status(ctx)
|
||||
elif data['content'] == 'devices':
|
||||
await get_snes_devices(ctx)
|
||||
elif data['content'] == 'gameInfo':
|
||||
ctx.ui_node.send_game_info(ctx)
|
||||
elif data['content'] == 'checkData':
|
||||
ctx.ui_node.send_location_check(ctx, 'Waiting for check...')
|
||||
elif data['type'] == 'webStatus':
|
||||
if data['content'] == 'connections':
|
||||
ctx.ui_node.send_connection_status(ctx)
|
||||
elif data['content'] == 'devices':
|
||||
await get_snes_devices(ctx)
|
||||
elif data['content'] == 'gameInfo':
|
||||
ctx.ui_node.send_game_info(ctx)
|
||||
elif data['content'] == 'checkData':
|
||||
ctx.ui_node.send_location_check(ctx, 'Waiting for check...')
|
||||
|
||||
elif data['type'] == 'webConfig':
|
||||
if 'serverAddress' in data['content']:
|
||||
ctx.server_address = data['content']['serverAddress']
|
||||
await connect(ctx, data['content']['serverAddress'])
|
||||
elif 'deviceId' in data['content']:
|
||||
# Allow a SNES disconnect via UI sending -1 as new device
|
||||
if data['content']['deviceId'] == "-1":
|
||||
ctx.ui_node.manual_snes = None
|
||||
ctx.snes_reconnect_address = None
|
||||
await snes_disconnect(ctx)
|
||||
else:
|
||||
await snes_disconnect(ctx)
|
||||
ctx.ui_node.manual_snes = data['content']['deviceId']
|
||||
await snes_connect(ctx, ctx.snes_address)
|
||||
elif data['type'] == 'webConfig':
|
||||
if 'serverAddress' in data['content']:
|
||||
ctx.server_address = data['content']['serverAddress']
|
||||
await connect(ctx, data['content']['serverAddress'])
|
||||
elif 'deviceId' in data['content']:
|
||||
# Allow a SNES disconnect via UI sending -1 as new device
|
||||
if data['content']['deviceId'] == "-1":
|
||||
ctx.ui_node.manual_snes = None
|
||||
ctx.snes_reconnect_address = None
|
||||
await snes_disconnect(ctx)
|
||||
else:
|
||||
await snes_disconnect(ctx)
|
||||
ctx.ui_node.manual_snes = data['content']['deviceId']
|
||||
await snes_connect(ctx, ctx.snes_address)
|
||||
|
||||
elif data['type'] == 'webControl':
|
||||
if 'disconnect' in data['content']:
|
||||
await ctx.disconnect()
|
||||
elif data['type'] == 'webControl':
|
||||
if 'disconnect' in data['content']:
|
||||
await ctx.disconnect()
|
||||
|
||||
elif data['type'] == 'webCommand':
|
||||
process_command(data['content'])
|
||||
|
||||
elif data['type'] == 'webCommand':
|
||||
process_command(data['content'])
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
if not isinstance(e, websockets.WebSocketException):
|
||||
logging.exception(e)
|
||||
|
|
|
@ -121,8 +121,8 @@ if __name__ == "__main__":
|
|||
seedname = segment
|
||||
break
|
||||
|
||||
multidataname = f"BM_{seedname}.multidata"
|
||||
spoilername = f"BM_{seedname}_Spoiler.txt"
|
||||
multidataname = f"AP_{seedname}.multidata"
|
||||
spoilername = f"AP_{seedname}_Spoiler.txt"
|
||||
romfilename = ""
|
||||
|
||||
if player_name:
|
||||
|
@ -158,7 +158,7 @@ if __name__ == "__main__":
|
|||
print(f"Removed {file} which is now present in the zipfile")
|
||||
|
||||
|
||||
zipname = os.path.join(output_path, f"BM_{seedname}.{typical_zip_ending}")
|
||||
zipname = os.path.join(output_path, f"AP_{seedname}.{typical_zip_ending}")
|
||||
|
||||
print(f"Creating zipfile {zipname}")
|
||||
ipv4 = (host if host else get_public_ipv4()) + ":" + str(port)
|
||||
|
@ -185,7 +185,7 @@ if __name__ == "__main__":
|
|||
if seedname in file:
|
||||
if file.endswith(".sfc"):
|
||||
futures.append(pool.submit(_handle_sfc_file, file))
|
||||
elif file.endswith(".bmbp"):
|
||||
elif file.endswith(".apbp"):
|
||||
futures.append(pool.submit(_handle_diff_file, file))
|
||||
|
||||
if zip_multidata and os.path.exists(os.path.join(output_path, multidataname)):
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import zlib
|
||||
import collections
|
||||
|
@ -13,6 +12,8 @@ import weakref
|
|||
import datetime
|
||||
import threading
|
||||
import random
|
||||
import pickle
|
||||
from json import loads, dumps
|
||||
|
||||
import ModuleUpdate
|
||||
|
||||
|
@ -26,7 +27,8 @@ from fuzzywuzzy import process as fuzzy_process
|
|||
import Items
|
||||
import Regions
|
||||
import Utils
|
||||
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem, _version_tuple
|
||||
from Utils import get_item_name_from_id, get_location_name_from_address, \
|
||||
ReceivedItem, _version_tuple, restricted_loads
|
||||
from NetUtils import Node, Endpoint
|
||||
|
||||
console_names = frozenset(set(Items.item_table) | set(Regions.location_table) | set(Items.item_name_groups))
|
||||
|
@ -106,28 +108,23 @@ class Context(Node):
|
|||
|
||||
def load(self, multidatapath: str, use_embedded_server_options: bool = False):
|
||||
with open(multidatapath, 'rb') as f:
|
||||
self._load(json.loads(zlib.decompress(f.read()).decode("utf-8-sig")),
|
||||
self._load(restricted_loads(zlib.decompress(f.read())),
|
||||
use_embedded_server_options)
|
||||
|
||||
self.data_filename = multidatapath
|
||||
|
||||
def _load(self, jsonobj: dict, use_embedded_server_options: bool):
|
||||
for team, names in enumerate(jsonobj['names']):
|
||||
def _load(self, decoded_obj: dict, use_embedded_server_options: bool):
|
||||
for team, names in enumerate(decoded_obj['names']):
|
||||
for player, name in enumerate(names, 1):
|
||||
self.player_names[(team, player)] = name
|
||||
|
||||
if "rom_strings" in jsonobj:
|
||||
self.rom_names = {rom: (team, slot) for slot, team, rom in jsonobj['rom_strings']}
|
||||
else:
|
||||
self.rom_names = {bytes(letter for letter in rom).decode(): (team, slot) for slot, team, rom in
|
||||
jsonobj['roms']}
|
||||
self.remote_items = set(jsonobj['remote_items'])
|
||||
self.locations = {tuple(k): tuple(v) for k, v in jsonobj['locations']}
|
||||
if "er_hint_data" in jsonobj:
|
||||
self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()}
|
||||
for player, loc_data in jsonobj["er_hint_data"].items()}
|
||||
self.rom_names = decoded_obj['roms']
|
||||
self.remote_items = decoded_obj['remote_items']
|
||||
self.locations = decoded_obj['locations']
|
||||
self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()}
|
||||
for player, loc_data in decoded_obj["er_hint_data"].items()}
|
||||
if use_embedded_server_options:
|
||||
server_options = jsonobj.get("server_options", {})
|
||||
server_options = decoded_obj.get("server_options", {})
|
||||
self._set_options(server_options)
|
||||
|
||||
def _set_options(self, server_options: dict):
|
||||
|
@ -154,9 +151,9 @@ class Context(Node):
|
|||
|
||||
def _save(self, exit_save:bool=False) -> bool:
|
||||
try:
|
||||
jsonstr = json.dumps(self.get_save())
|
||||
encoded_save = pickle.dumps(self.get_save())
|
||||
with open(self.save_filename, "wb") as f:
|
||||
f.write(zlib.compress(jsonstr.encode("utf-8")))
|
||||
f.write(zlib.compress(encoded_save))
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return False
|
||||
|
@ -171,8 +168,8 @@ class Context(Node):
|
|||
self.data_filename + '_')) + 'multisave'
|
||||
try:
|
||||
with open(self.save_filename, 'rb') as f:
|
||||
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
|
||||
self.set_save(jsonobj)
|
||||
save_data = restricted_loads(zlib.decompress(f.read()))
|
||||
self.set_save(save_data)
|
||||
except FileNotFoundError:
|
||||
logging.error('No save data found, starting a new game')
|
||||
except Exception as e:
|
||||
|
@ -280,16 +277,21 @@ class Context(Node):
|
|||
logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text))
|
||||
asyncio.create_task(self.send_msgs(client, [['Print', text]]))
|
||||
|
||||
def notify_client_multiple(self, client: Client, texts: typing.List[str]):
|
||||
if not client.auth:
|
||||
return
|
||||
asyncio.create_task(self.send_msgs(client, [['Print', text] for text in texts]))
|
||||
|
||||
def broadcast_team(self, team, msgs):
|
||||
for client in self.endpoints:
|
||||
if client.auth and client.team == team:
|
||||
asyncio.create_task(self.send_msgs(client, msgs))
|
||||
|
||||
def broadcast_all(self, msgs):
|
||||
msgs = json.dumps(msgs)
|
||||
msgs = dumps(msgs)
|
||||
for endpoint in self.endpoints:
|
||||
if endpoint.auth:
|
||||
asyncio.create_task(self.send_json_msgs(endpoint, msgs))
|
||||
asyncio.create_task(self.send_encoded_msgs(endpoint, msgs))
|
||||
|
||||
async def disconnect(self, endpoint):
|
||||
await super(Context, self).disconnect(endpoint)
|
||||
|
@ -298,26 +300,25 @@ class Context(Node):
|
|||
|
||||
# separated out, due to compatibilty between clients
|
||||
def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]):
|
||||
cmd = json.dumps([["Hint", hints]]) # make sure it is a list, as it can be set internally
|
||||
cmd = dumps([["Hint", hints]]) # make sure it is a list, as it can be set internally
|
||||
texts = [['Print', format_hint(ctx, team, hint)] for hint in hints]
|
||||
for _, text in texts:
|
||||
logging.info("Notice (Team #%d): %s" % (team + 1, text))
|
||||
|
||||
for client in ctx.endpoints:
|
||||
if client.auth and client.team == team:
|
||||
asyncio.create_task(ctx.send_json_msgs(client, cmd))
|
||||
asyncio.create_task(ctx.send_encoded_msgs(client, cmd))
|
||||
|
||||
|
||||
def update_aliases(ctx: Context, team: int, client: typing.Optional[Client] = None):
|
||||
cmd = json.dumps([["AliasUpdate",
|
||||
cmd = dumps([["AliasUpdate",
|
||||
[(key[1], ctx.get_aliased_name(*key)) for key, value in ctx.player_names.items() if
|
||||
key[0] == team]]])
|
||||
if client is None:
|
||||
for client in ctx.endpoints:
|
||||
if client.team == team and client.auth and client.version > [2, 0, 3]:
|
||||
asyncio.create_task(ctx.send_json_msgs(client, cmd))
|
||||
if client.team == team and client.auth:
|
||||
asyncio.create_task(ctx.send_encoded_msgs(client, cmd))
|
||||
else:
|
||||
asyncio.create_task(ctx.send_json_msgs(client, cmd))
|
||||
asyncio.create_task(ctx.send_encoded_msgs(client, cmd))
|
||||
|
||||
|
||||
async def server(websocket, path, ctx: Context):
|
||||
|
@ -327,7 +328,7 @@ async def server(websocket, path, ctx: Context):
|
|||
try:
|
||||
await on_client_connected(ctx, client)
|
||||
async for data in websocket:
|
||||
for msg in json.loads(data):
|
||||
for msg in loads(data):
|
||||
if len(msg) == 1:
|
||||
cmd = msg
|
||||
args = None
|
||||
|
@ -392,7 +393,7 @@ async def countdown(ctx: Context, timer):
|
|||
|
||||
async def missing(ctx: Context, client: Client, locations: list):
|
||||
await ctx.send_msgs(client, [['Missing', {
|
||||
'locations': json.dumps(locations)
|
||||
'locations': dumps(locations)
|
||||
}]])
|
||||
|
||||
|
||||
|
@ -762,6 +763,9 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
self.output(
|
||||
"Sorry, client forfeiting requires you to have beaten the game on this server."
|
||||
" You can ask the server admin for a /forfeit")
|
||||
if self.client.version < [2, 1, 0]:
|
||||
self.output(
|
||||
"Your client is too old to send game beaten information. Please update, load you savegame and reconnect.")
|
||||
return False
|
||||
|
||||
def _cmd_remaining(self) -> bool:
|
||||
|
@ -805,13 +809,9 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
locations.append(location_name)
|
||||
|
||||
if len(locations) > 0:
|
||||
if self.client.version < [2, 3, 0]:
|
||||
buffer = ""
|
||||
for location in locations:
|
||||
buffer += f'Missing: {location}\n'
|
||||
self.output(buffer + f"Found {len(locations)} missing location checks")
|
||||
else:
|
||||
asyncio.create_task(missing(self.ctx, self.client, locations))
|
||||
texts = [f'Missing: {location}\n' for location in locations]
|
||||
texts.append(f"Found {len(locations)} missing location checks")
|
||||
self.ctx.notify_client_multiple(self.client, texts)
|
||||
else:
|
||||
self.output("No missing location checks found.")
|
||||
return True
|
||||
|
@ -954,6 +954,7 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args):
|
|||
if type(args["rom"]) == list:
|
||||
args["rom"] = bytes(letter for letter in args["rom"]).decode()
|
||||
if args['rom'] not in ctx.rom_names:
|
||||
logging.info((args["rom"], ctx.rom_names))
|
||||
errors.add('InvalidRom')
|
||||
else:
|
||||
team, slot = ctx.rom_names[args['rom']]
|
||||
|
@ -972,7 +973,7 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args):
|
|||
client.name = ctx.player_names[(team, slot)]
|
||||
client.team = team
|
||||
client.slot = slot
|
||||
if "AP" not in args.get('tags', Client.tags):
|
||||
if ctx.compatibility == 1 and "AP" not in args.get('tags', Client.tags):
|
||||
errors.add('IncompatibleVersion')
|
||||
elif ctx.compatibility == 0 and args.get('version', Client.version) != list(_version_tuple):
|
||||
errors.add('IncompatibleVersion')
|
||||
|
|
13
NetUtils.py
13
NetUtils.py
|
@ -1,33 +1,34 @@
|
|||
from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import typing
|
||||
from json import loads, dumps
|
||||
|
||||
import websockets
|
||||
|
||||
|
||||
class Node:
|
||||
endpoints: typing.List
|
||||
dumper = staticmethod(dumps)
|
||||
loader = staticmethod(loads)
|
||||
|
||||
def __init__(self):
|
||||
self.endpoints = []
|
||||
|
||||
def broadcast_all(self, msgs):
|
||||
msgs = json.dumps(msgs)
|
||||
msgs = self.dumper(msgs)
|
||||
for endpoint in self.endpoints:
|
||||
asyncio.create_task(self.send_json_msgs(endpoint, msgs))
|
||||
asyncio.create_task(self.send_encoded_msgs(endpoint, msgs))
|
||||
|
||||
async def send_msgs(self, endpoint: Endpoint, msgs):
|
||||
if not endpoint.socket or not endpoint.socket.open or endpoint.socket.closed:
|
||||
return
|
||||
try:
|
||||
await endpoint.socket.send(json.dumps(msgs))
|
||||
await endpoint.socket.send(self.dumper(msgs))
|
||||
except websockets.ConnectionClosed:
|
||||
logging.exception("Exception during send_msgs")
|
||||
await self.disconnect(endpoint)
|
||||
|
||||
async def send_json_msgs(self, endpoint: Endpoint, msg: str):
|
||||
async def send_encoded_msgs(self, endpoint: Endpoint, msg: str):
|
||||
if not endpoint.socket or not endpoint.socket.open or endpoint.socket.closed:
|
||||
return
|
||||
try:
|
||||
|
|
6
Patch.py
6
Patch.py
|
@ -57,7 +57,7 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str
|
|||
bytes = generate_patch(load_bytes(rom_file_to_patch),
|
||||
{
|
||||
"server": server}) # allow immediate connection to server in multiworld. Empty string otherwise
|
||||
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".bmbp"
|
||||
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".apbp"
|
||||
write_lzma(bytes, target)
|
||||
return target
|
||||
|
||||
|
@ -110,7 +110,7 @@ if __name__ == "__main__":
|
|||
result = pool.submit(create_patch_file, rom, address)
|
||||
result.add_done_callback(lambda task: print(f"Created patch {task.result()}"))
|
||||
|
||||
elif rom.endswith(".bmbp"):
|
||||
elif rom.endswith(".apbp"):
|
||||
print(f"Applying patch {rom}")
|
||||
data, target = create_rom_file(rom)
|
||||
romfile, adjusted = Utils.get_adjuster_settings(target)
|
||||
|
@ -147,7 +147,7 @@ if __name__ == "__main__":
|
|||
|
||||
def _handle_zip_file_entry(zfinfo : zipfile.ZipInfo, server: str):
|
||||
data = zfr.read(zfinfo)
|
||||
if zfinfo.filename.endswith(".bmbp"):
|
||||
if zfinfo.filename.endswith(".apbp"):
|
||||
data = update_patch_data(data, server)
|
||||
with ziplock:
|
||||
zfw.writestr(zfinfo, data)
|
||||
|
|
7
Rom.py
7
Rom.py
|
@ -79,12 +79,12 @@ class LocalRom(object):
|
|||
|
||||
if self.verify(buffer):
|
||||
self.buffer = buffer
|
||||
if not os.path.exists(local_path('data', 'basepatch.bmbp')):
|
||||
if not os.path.exists(local_path('data', 'basepatch.apbp')):
|
||||
Patch.create_patch_file(local_path('basepatch.sfc'))
|
||||
return
|
||||
|
||||
if os.path.isfile(local_path('data', 'basepatch.bmbp')):
|
||||
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.bmbp'))
|
||||
if os.path.isfile(local_path('data', 'basepatch.apbp')):
|
||||
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.apbp'))
|
||||
if self.verify(buffer):
|
||||
self.buffer = bytearray(buffer)
|
||||
with open(local_path('basepatch.sfc'), 'wb') as stream:
|
||||
|
@ -1398,6 +1398,7 @@ def patch_rom(world, rom, player, team, enemized):
|
|||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
# TODO: Adjust Enemizer to accept AP and AD
|
||||
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{team + 1}_{player}_{world.seed:09}\0', 'utf8')[:21]
|
||||
rom.name.extend([0] * (21 - len(rom.name)))
|
||||
rom.write_bytes(0x7FC0, rom.name)
|
||||
|
|
|
@ -20,7 +20,7 @@ def download_patch(room_id, patch_id):
|
|||
patch_data = update_patch_data(patch.data, server="berserkermulti.world:" + str(last_port))
|
||||
patch_data = io.BytesIO(patch_data)
|
||||
|
||||
fname = f"P{patch.player}_{pname}_{app.jinja_env.filters['suuid'](room_id)}.bmbp"
|
||||
fname = f"P{patch.player}_{pname}_{app.jinja_env.filters['suuid'](room_id)}.apbp"
|
||||
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
|
||||
|
||||
|
||||
|
@ -43,5 +43,5 @@ def download_raw_patch(seed_id, player_id):
|
|||
patch_data = update_patch_data(patch.data, server="")
|
||||
patch_data = io.BytesIO(patch_data)
|
||||
|
||||
fname = f"P{patch.player}_{pname}_{app.jinja_env.filters['suuid'](seed_id)}.bmbp"
|
||||
fname = f"P{patch.player}_{pname}_{app.jinja_env.filters['suuid'](seed_id)}.apbp"
|
||||
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
|
||||
|
|
|
@ -123,7 +123,7 @@ def upload_to_db(folder, owner, sid):
|
|||
multidata = None
|
||||
for file in os.listdir(folder):
|
||||
file = os.path.join(folder, file)
|
||||
if file.endswith(".bmbp"):
|
||||
if file.endswith(".apbp"):
|
||||
player = int(file.split("P")[1].split(".")[0].split("_")[0])
|
||||
patches.add(Patch(data=open(file, "rb").read(), player=player))
|
||||
elif file.endswith(".txt"):
|
||||
|
|
|
@ -8,7 +8,7 @@ from pony.orm import commit, select
|
|||
|
||||
from WebHostLib import app, Seed, Room, Patch
|
||||
|
||||
accepted_zip_contents = {"patches": ".bmbp",
|
||||
accepted_zip_contents = {"patches": ".apbp",
|
||||
"spoiler": ".txt",
|
||||
"multidata": "multidata"}
|
||||
|
||||
|
@ -38,7 +38,7 @@ def uploads():
|
|||
for file in infolist:
|
||||
if file.filename.endswith(banned_zip_contents):
|
||||
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. Your file was deleted."
|
||||
elif file.filename.endswith(".bmbp"):
|
||||
elif file.filename.endswith(".apbp"):
|
||||
player = int(file.filename.split("P")[1].split(".")[0].split("_")[0])
|
||||
patches.add(Patch(data=zfile.open(file, "r").read(), player=player))
|
||||
elif file.filename.endswith(".txt"):
|
||||
|
|
5
WebUI.py
5
WebUI.py
|
@ -1,6 +1,6 @@
|
|||
import http.server
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import socketserver
|
||||
import threading
|
||||
|
@ -16,6 +16,9 @@ logger = logging.getLogger("WebUIRelay")
|
|||
|
||||
|
||||
class WebUiClient(Node):
|
||||
loader = staticmethod(json.loads)
|
||||
dumper = staticmethod(json.dumps)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.manual_snes = None
|
||||
|
|
|
@ -57,7 +57,7 @@ Type: dirifempty; Name: "{app}"
|
|||
|
||||
[Registry]
|
||||
|
||||
Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""
|
||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""
|
||||
Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Berserker's Multiworld 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: ""
|
||||
|
|
Loading…
Reference in New Issue