diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index ed5e6cf102..58efda6c04 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -1,11 +1,9 @@ import binascii import importlib.util import importlib.machinery -import os import random import pickle import Utils -import settings from collections import defaultdict from typing import Dict @@ -65,8 +63,27 @@ from .patches.aesthetics import rgb_to_bin, bin_to_rgb from .. import Options +class VersionError(Exception): + pass + # Function to generate a final rom, this patches the rom with all required patches 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"]) multi_key = binascii.unhexlify(patch_data["multi_key"].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) if options["gfxmod"]: - user_settings = settings.get_settings() 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) except FileNotFoundError: pass # if user just doesnt provide gfxmod file, let patching continue diff --git a/worlds/ladx/LinksAwakeningClient.py b/worlds/ladx/LinksAwakeningClient.py index 70bd4d8b9b..b192b264e7 100644 --- a/worlds/ladx/LinksAwakeningClient.py +++ b/worlds/ladx/LinksAwakeningClient.py @@ -47,6 +47,10 @@ class BadRetroArchResponse(GameboyException): pass +class VersionError(Exception): + pass + + class LAClientConstants: # Connector version VERSION = 0x01 @@ -518,7 +522,7 @@ class LinksAwakeningContext(CommonContext): ("Client", "Archipelago"), ("Tracker", "Tracker"), ] - base_title = "Archipelago Links Awakening DX Client" + base_title = f"Links Awakening DX Client {LinksAwakeningWorld.world_version.as_simple_string()} | Archipelago" def build(self): b = super().build() @@ -614,11 +618,20 @@ class LinksAwakeningContext(CommonContext): 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()})" + ) # This is sent to magpie over local websocket to make its own connection self.slot_data.update({ "server_address": self.server_address, "slot_name": self.player_names[self.slot], "password": self.password, + "client_version": client_version.as_simple_string(), }) # We can process linked items on already-checked checks now that we have slot_data diff --git a/worlds/ladx/Rom.py b/worlds/ladx/Rom.py index c58403c7a2..e0ee4b98ea 100644 --- a/worlds/ladx/Rom.py +++ b/worlds/ladx/Rom.py @@ -1,4 +1,3 @@ -import settings import worlds.Files import hashlib import Utils @@ -59,6 +58,7 @@ class LADXProcedurePatch(worlds.Files.APProcedurePatch): 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)]) data_dict = { + "generated_world_version": world.world_version.as_simple_string(), "out_base": world.multiworld.get_out_file_name_base(patch.player), "is_race": world.multiworld.is_race, "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: - options = settings.get_settings() + from . import LinksAwakeningWorld if not file_name: - file_name = options["ladx_options"]["rom_file"] + file_name = LinksAwakeningWorld.settings.rom_file if not os.path.exists(file_name): file_name = Utils.user_path(file_name) return file_name diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index f112d52503..c9f907046a 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -4,8 +4,10 @@ import os import typing import logging import re +import struct import settings +import Utils from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial from Fill import fill_restrictive from worlds.AutoWorld import WebWorld, World @@ -50,6 +52,17 @@ class LinksAwakeningSettings(settings.Group): description = "LADX ROM File" 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): """ 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 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("