WebHost: handle SM and SoE
This commit is contained in:
parent
c178006acc
commit
fc3b8c40be
24
Patch.py
24
Patch.py
|
@ -1,3 +1,5 @@
|
||||||
|
# TODO: convert this into a system like AutoWorld
|
||||||
|
|
||||||
import bsdiff4
|
import bsdiff4
|
||||||
import yaml
|
import yaml
|
||||||
import os
|
import os
|
||||||
|
@ -14,16 +16,25 @@ current_patch_version = 3
|
||||||
|
|
||||||
GAME_ALTTP = "A Link to the Past"
|
GAME_ALTTP = "A Link to the Past"
|
||||||
GAME_SM = "Super Metroid"
|
GAME_SM = "Super Metroid"
|
||||||
supported_games = {"A Link to the Past", "Super Metroid"}
|
GAME_SOE = "Secret of Evermore"
|
||||||
|
supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore"}
|
||||||
|
|
||||||
|
preferred_endings = {
|
||||||
|
GAME_ALTTP: "apbp",
|
||||||
|
GAME_SM: "apm3",
|
||||||
|
GAME_SOE: "apsoe"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes:
|
def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes:
|
||||||
if game == GAME_ALTTP:
|
if game == GAME_ALTTP:
|
||||||
from worlds.alttp.Rom import JAP10HASH
|
from worlds.alttp.Rom import JAP10HASH as HASH
|
||||||
elif game == GAME_SM:
|
elif game == GAME_SM:
|
||||||
from worlds.sm.Rom import JAP10HASH
|
from worlds.sm.Rom import JAP10HASH as HASH
|
||||||
|
elif game == GAME_SOE:
|
||||||
|
from worlds.soe.Patch import USHASH as HASH
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Selected game for base rom not found.")
|
raise RuntimeError(f"Selected game {game} for base rom not found.")
|
||||||
|
|
||||||
patch = yaml.dump({"meta": metadata,
|
patch = yaml.dump({"meta": metadata,
|
||||||
"patch": patch,
|
"patch": patch,
|
||||||
|
@ -31,7 +42,7 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAM
|
||||||
# minimum version of patch system expected for patching to be successful
|
# minimum version of patch system expected for patching to be successful
|
||||||
"compatible_version": 3,
|
"compatible_version": 3,
|
||||||
"version": current_patch_version,
|
"version": current_patch_version,
|
||||||
"base_checksum": JAP10HASH})
|
"base_checksum": HASH})
|
||||||
return patch.encode(encoding="utf-8-sig")
|
return patch.encode(encoding="utf-8-sig")
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +51,9 @@ def generate_patch(rom: bytes, metadata: Optional[dict] = None, game: str = GAME
|
||||||
from worlds.alttp.Rom import get_base_rom_bytes
|
from worlds.alttp.Rom import get_base_rom_bytes
|
||||||
elif game == GAME_SM:
|
elif game == GAME_SM:
|
||||||
from worlds.sm.Rom import get_base_rom_bytes
|
from worlds.sm.Rom import get_base_rom_bytes
|
||||||
|
elif game == GAME_SOE:
|
||||||
|
file_name = Utils.get_options()["soe_options"]["rom"]
|
||||||
|
get_base_rom_bytes = lambda: bytes(read_rom(open(file_name, "rb")))
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Selected game for base rom not found.")
|
raise RuntimeError("Selected game for base rom not found.")
|
||||||
|
|
||||||
|
|
3
Utils.py
3
Utils.py
|
@ -166,6 +166,9 @@ def get_default_options() -> dict:
|
||||||
"sni": "SNI",
|
"sni": "SNI",
|
||||||
"rom_start": True,
|
"rom_start": True,
|
||||||
},
|
},
|
||||||
|
"soe_options": {
|
||||||
|
"rom_file": "Secret of Evermore (USA).sfc",
|
||||||
|
},
|
||||||
"lttp_options": {
|
"lttp_options": {
|
||||||
"rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc",
|
"rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc",
|
||||||
"sni": "SNI",
|
"sni": "SNI",
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from flask import send_file, Response, render_template
|
from flask import send_file, Response, render_template
|
||||||
from pony.orm import select
|
from pony.orm import select
|
||||||
|
|
||||||
from Patch import update_patch_data
|
from Patch import update_patch_data, preferred_endings
|
||||||
from WebHostLib import app, Slot, Room, Seed, cache
|
from WebHostLib import app, Slot, Room, Seed, cache
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
||||||
def download_patch(room_id, patch_id):
|
def download_patch(room_id, patch_id):
|
||||||
patch = Slot.get(id=patch_id)
|
patch = Slot.get(id=patch_id)
|
||||||
|
@ -19,7 +20,8 @@ def download_patch(room_id, patch_id):
|
||||||
patch_data = update_patch_data(patch.data, server=f"{app.config['PATCH_TARGET']}:{last_port}")
|
patch_data = update_patch_data(patch.data, server=f"{app.config['PATCH_TARGET']}:{last_port}")
|
||||||
patch_data = io.BytesIO(patch_data)
|
patch_data = io.BytesIO(patch_data)
|
||||||
|
|
||||||
fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}.apbp"
|
fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}." \
|
||||||
|
f"{preferred_endings[patch.game]}"
|
||||||
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
|
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,23 +30,6 @@ def download_spoiler(seed_id):
|
||||||
return Response(Seed.get(id=seed_id).spoiler, mimetype="text/plain")
|
return Response(Seed.get(id=seed_id).spoiler, mimetype="text/plain")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/dl_raw_patch/<suuid:seed_id>/<int:player_id>")
|
|
||||||
def download_raw_patch(seed_id, player_id: int):
|
|
||||||
seed = Seed.get(id=seed_id)
|
|
||||||
patch = select(patch for patch in seed.slots if
|
|
||||||
patch.player_id == player_id).first()
|
|
||||||
|
|
||||||
if not patch:
|
|
||||||
return "Patch not found"
|
|
||||||
else:
|
|
||||||
import io
|
|
||||||
|
|
||||||
patch_data = update_patch_data(patch.data, server="")
|
|
||||||
patch_data = io.BytesIO(patch_data)
|
|
||||||
|
|
||||||
fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](seed_id)}.apbp"
|
|
||||||
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
|
|
||||||
|
|
||||||
@app.route("/slot_file/<suuid:room_id>/<int:player_id>")
|
@app.route("/slot_file/<suuid:room_id>/<int:player_id>")
|
||||||
def download_slot_file(room_id, player_id: int):
|
def download_slot_file(room_id, player_id: int):
|
||||||
room = Room.get(id=room_id)
|
room = Room.get(id=room_id)
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
<td><a href="{{ url_for("download_spoiler", seed_id=seed.id) }}">Download</a></td>
|
<td><a href="{{ url_for("download_spoiler", seed_id=seed.id) }}">Download</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if seed.multidata %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rooms: </td>
|
<td>Rooms: </td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -39,23 +38,6 @@
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td>Files: </td>
|
|
||||||
<td>
|
|
||||||
<ul>
|
|
||||||
{% for slot in seed.slots %}
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="{{ url_for("download_raw_patch", seed_id=seed.id, player_id=slot.player_id) }}">Player {{ slot.player_name }}</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,10 +10,7 @@ from pony.orm import flush, select
|
||||||
|
|
||||||
from WebHostLib import app, Seed, Room, Slot
|
from WebHostLib import app, Seed, Room, Slot
|
||||||
from Utils import parse_yaml
|
from Utils import parse_yaml
|
||||||
|
from Patch import preferred_endings
|
||||||
accepted_zip_contents = {"patches": ".apbp",
|
|
||||||
"spoiler": ".txt",
|
|
||||||
"multidata": ".archipelago"}
|
|
||||||
|
|
||||||
banned_zip_contents = (".sfc",)
|
banned_zip_contents = (".sfc",)
|
||||||
|
|
||||||
|
@ -29,15 +26,17 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s
|
||||||
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. " \
|
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \
|
||||||
"Your file was deleted."
|
"Your file was deleted."
|
||||||
elif file.filename.endswith(".apbp"):
|
elif file.filename.endswith(tuple(preferred_endings.values())):
|
||||||
data = zfile.open(file, "r").read()
|
data = zfile.open(file, "r").read()
|
||||||
yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig"))
|
yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig"))
|
||||||
if yaml_data["version"] < 2:
|
if yaml_data["version"] < 2:
|
||||||
return "Old format cannot be uploaded (outdated .apbp)", 500
|
return "Old format cannot be uploaded (outdated .apbp)"
|
||||||
metadata = yaml_data["meta"]
|
metadata = yaml_data["meta"]
|
||||||
slots.add(Slot(data=data, player_name=metadata["player_name"],
|
|
||||||
|
slots.add(Slot(data=data,
|
||||||
|
player_name=metadata["player_name"],
|
||||||
player_id=metadata["player_id"],
|
player_id=metadata["player_id"],
|
||||||
game="A Link to the Past"))
|
game=yaml_data["game"]))
|
||||||
|
|
||||||
elif file.filename.endswith(".apmc"):
|
elif file.filename.endswith(".apmc"):
|
||||||
data = zfile.open(file, "r").read()
|
data = zfile.open(file, "r").read()
|
||||||
|
|
|
@ -204,7 +204,12 @@ class SoEWorld(World):
|
||||||
flags, money, exp)):
|
flags, money, exp)):
|
||||||
raise RuntimeError()
|
raise RuntimeError()
|
||||||
with lzma.LZMAFile(patch_file, 'wb') as f:
|
with lzma.LZMAFile(patch_file, 'wb') as f:
|
||||||
f.write(generate_patch(rom_file, out_file))
|
f.write(generate_patch(rom_file, out_file,
|
||||||
|
{
|
||||||
|
# used by WebHost
|
||||||
|
"player_name": self.world.player_name[self.player],
|
||||||
|
"player_id": self.player
|
||||||
|
}))
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
|
Loading…
Reference in New Issue