WebHost: handle SM and SoE

This commit is contained in:
Fabian Dill 2021-11-13 20:52:30 +01:00
parent c178006acc
commit fc3b8c40be
6 changed files with 44 additions and 56 deletions

View File

@ -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.")

View File

@ -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",

View File

@ -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)

View File

@ -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:&nbsp;</td> <td>Rooms:&nbsp;</td>
<td> <td>
@ -39,23 +38,6 @@
{% endcall %} {% endcall %}
</td> </td>
</tr> </tr>
{% else %}
<tr>
<td>Files:&nbsp;</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>

View File

@ -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()

View File

@ -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: