LADX: Give better feedback during patching (#5401)

This commit is contained in:
threeandthreee
2025-11-25 14:42:55 -05:00
committed by GitHub
parent aa2774a5d5
commit f3000a89d4
5 changed files with 69 additions and 9 deletions

View File

@@ -1,11 +1,9 @@
import binascii import binascii
import importlib.util import importlib.util
import importlib.machinery import importlib.machinery
import os
import random import random
import pickle import pickle
import Utils import Utils
import settings
from collections import defaultdict from collections import defaultdict
from typing import Dict from typing import Dict
@@ -65,8 +63,27 @@ from .patches.aesthetics import rgb_to_bin, bin_to_rgb
from .. import Options from .. import Options
class VersionError(Exception):
pass
# Function to generate a final rom, this patches the rom with all required patches # Function to generate a final rom, this patches the rom with all required patches
def generateRom(base_rom: bytes, args, patch_data: Dict): def generateRom(base_rom: bytes, args, patch_data: Dict):
from .. import LinksAwakeningWorld
patcher_version = LinksAwakeningWorld.world_version
generated_version = Utils.tuplize_version(patch_data.get("generated_world_version", "2.0.0"))
if generated_version.major != patcher_version.major or generated_version.minor != patcher_version.minor:
Utils.messagebox(
"Error",
"The apworld version that this patch was generated on is incompatible with your installed world.\n\n"
f"Generated on {generated_version.as_simple_string()}\n"
f"Installed version {patcher_version.as_simple_string()}",
True
)
raise VersionError(
f"The installed world ({patcher_version.as_simple_string()}) is incompatible with the world this patch "
f"was generated on ({generated_version.as_simple_string()})"
)
random.seed(patch_data["seed"] + patch_data["player"]) random.seed(patch_data["seed"] + patch_data["player"])
multi_key = binascii.unhexlify(patch_data["multi_key"].encode()) multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode())) item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
@@ -85,9 +102,8 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
pymod.prePatch(rom) pymod.prePatch(rom)
if options["gfxmod"]: if options["gfxmod"]:
user_settings = settings.get_settings()
try: try:
gfx_mod_file = user_settings["ladx_options"]["gfx_mod_file"] gfx_mod_file = LinksAwakeningWorld.settings.gfx_mod_file
patches.aesthetics.gfxMod(rom, gfx_mod_file) patches.aesthetics.gfxMod(rom, gfx_mod_file)
except FileNotFoundError: except FileNotFoundError:
pass # if user just doesnt provide gfxmod file, let patching continue pass # if user just doesnt provide gfxmod file, let patching continue

View File

@@ -47,6 +47,10 @@ class BadRetroArchResponse(GameboyException):
class BadRetroArchResponse(GameboyException): class BadRetroArchResponse(GameboyException):
pass pass
class VersionError(Exception):
pass
class LAClientConstants: class LAClientConstants:
@@ -518,7 +522,7 @@ class LinksAwakeningContext(CommonContext):
class LADXManager(GameManager): class LADXManager(GameManager):
logging_pairs = [ logging_pairs = [
("Client", "Archipelago"), ("Client", "Archipelago"),
("Tracker", "Tracker"), ("Tracker", "Tracker"),
] ]
base_title = f"Links Awakening DX Client {LinksAwakeningWorld.world_version.as_simple_string()} | Archipelago" base_title = f"Links Awakening DX Client {LinksAwakeningWorld.world_version.as_simple_string()} | Archipelago"
@@ -614,11 +618,20 @@ class LinksAwakeningContext(CommonContext):
def on_package(self, cmd: str, args: dict): def on_package(self, cmd: str, args: dict):
if cmd == "Connected": if cmd == "Connected":
self.game = self.slot_info[self.slot].game
self.slot_data = args.get("slot_data", {})
generated_version = Utils.tuplize_version(self.slot_data.get("world_version", "2.0.0"))
client_version = LinksAwakeningWorld.world_version
if generated_version.major != client_version.major:
self.disconnected_intentionally = True
raise VersionError(
f"The installed world ({client_version.as_simple_string()}) is incompatible with "
f"the world this game was generated on ({generated_version.as_simple_string()})" f"the world this game was generated on ({generated_version.as_simple_string()})"
) )
# This is sent to magpie over local websocket to make its own connection # This is sent to magpie over local websocket to make its own connection
self.slot_data.update({ self.slot_data.update({
"server_address": self.server_address, "server_address": self.server_address,
"slot_name": self.player_names[self.slot],
"password": self.password, "password": self.password,
"client_version": client_version.as_simple_string(), "client_version": client_version.as_simple_string(),
}) })

View File

@@ -1,4 +1,3 @@
import settings
import worlds.Files import worlds.Files
import hashlib import hashlib
import Utils import Utils
@@ -59,6 +58,7 @@ class LADXProcedurePatch(worlds.Files.APProcedurePatch):
def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch): def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch):
item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]) item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)])
data_dict = { data_dict = {
"generated_world_version": world.world_version.as_simple_string(),
"out_base": world.multiworld.get_out_file_name_base(patch.player), "out_base": world.multiworld.get_out_file_name_base(patch.player),
"is_race": world.multiworld.is_race, "is_race": world.multiworld.is_race,
"seed": world.multiworld.seed, "seed": world.multiworld.seed,
@@ -125,9 +125,9 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
def get_base_rom_path(file_name: str = "") -> str: def get_base_rom_path(file_name: str = "") -> str:
options = settings.get_settings() from . import LinksAwakeningWorld
if not file_name: if not file_name:
file_name = options["ladx_options"]["rom_file"] file_name = LinksAwakeningWorld.settings.rom_file
if not os.path.exists(file_name): if not os.path.exists(file_name):
file_name = Utils.user_path(file_name) file_name = Utils.user_path(file_name)
return file_name return file_name

View File

@@ -4,8 +4,10 @@ import os
import typing import typing
import logging import logging
import re import re
import struct
import settings import settings
import Utils
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial
from Fill import fill_restrictive from Fill import fill_restrictive
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
@@ -50,6 +52,17 @@ class LinksAwakeningSettings(settings.Group):
description = "LADX ROM File" description = "LADX ROM File"
md5s = [LADXProcedurePatch.hash] md5s = [LADXProcedurePatch.hash]
@classmethod
def validate(cls, path: str) -> None:
try:
super().validate(path)
except ValueError:
Utils.messagebox(
"Error",
"Provided rom does not match hash for English 1.0/revision-0 of Link's Awakening DX",
True)
raise
class RomStart(str): class RomStart(str):
""" """
Set this to false to never autostart a rom (such as after patching) Set this to false to never autostart a rom (such as after patching)
@@ -71,6 +84,24 @@ class LinksAwakeningSettings(settings.Group):
Only .bin or .bdiff files Only .bin or .bdiff files
The same directory will be checked for a matching text modification file The same directory will be checked for a matching text modification file
""" """
def browse(self, filetypes=None, **kwargs):
filetypes = [("Binary / Patch files", [".bin", ".bdiff"])]
return super().browse(filetypes=filetypes, **kwargs)
@classmethod
def validate(cls, path: str) -> None:
with open(path, "rb", buffering=0) as f:
header, size = struct.unpack("<II", f.read()[:8])
if path.endswith('.bin') and header == 0xDEADBEEF and size < 1024:
# detect extended spritesheets from upstream ladxr
Utils.messagebox(
"Error",
"Extended sprite sheets are not supported. Try again with a different gfxmod file, "
"or provide no file to continue without modifying graphics.",
True)
raise ValueError("Provided gfxmod file is an extended sheet, which is not supported")
rom_file: RomFile = RomFile(RomFile.copy_to) rom_file: RomFile = RomFile(RomFile.copy_to)
rom_start: typing.Union[RomStart, bool] = True rom_start: typing.Union[RomStart, bool] = True

View File

@@ -2,5 +2,5 @@
"game": "Links Awakening DX", "game": "Links Awakening DX",
"authors": [ "zig", "threeandthree" ], "authors": [ "zig", "threeandthree" ],
"minimum_ap_version": "0.6.4", "minimum_ap_version": "0.6.4",
"world_version": "2.0.0" "world_version": "2.0.1"
} }