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