diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index cbc3031..34ebf76 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -28,8 +28,14 @@ jobs: with: python-version: "3.9" - - name: Install Python packages - run: python -m pip install --upgrade pip build + - name: Install core Python packages + run: python -m pip install --upgrade pip build + + - name: Install source python packages + run: python -m pip install -e ".[tooling]" + + - name: Fetch the corresponding assembly patches + run: python pull-assembly-patches.py - name: build # Ideally, we'd have PYTHONWARNINGS=error here, but diff --git a/README.md b/README.md index 406654c..3833320 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,7 @@ Running from source: - Unix-based: `source ./venv/bin/activate` - Install the project as editable: `pip install -e .` - Run: `python -m mars_patcher` + +Before running the patcher, you want to initialize the required assembly patches into `src/mars_patcher/data/patches/mf_u/asm`. +The easiest way to do that is by running `python pull-assembly-patches.py`, which will fetch the patches from the correct release. +However for development purposes, you may want to create the assembly patches yourself manually and then copy them to that directory. diff --git a/pull-assembly-patches.py b/pull-assembly-patches.py new file mode 100644 index 0000000..ce5b581 --- /dev/null +++ b/pull-assembly-patches.py @@ -0,0 +1,41 @@ +import shutil +import tempfile +import urllib.request +from pathlib import Path +from zipfile import ZipFile + +import requests + +VERSION = "0.1.8" +ASSET_NAME = "Randomizer.Patches.zip" +DESTINATION_ASSEMBLY_PATH = ( + Path(__file__) + .parent.resolve() + .joinpath("src", "mars_patcher", "data", "patches", "mf_u", "asm") +) + + +release_url = f"https://api.github.com/repos/MetroidAdvRandomizerSystem/mars-fusion-asm/releases/tags/{VERSION}" +print(f"Fetching {release_url}") +response = requests.get(release_url).json() + + +for asset in response["assets"]: + if asset["name"] != ASSET_NAME: + continue + + print("Correct release found, initalizing download") + with tempfile.TemporaryDirectory() as temp_ref: + temp_dir = Path(temp_ref) + temp_file = temp_dir.joinpath("cache.zip") + urllib.request.urlretrieve(asset["browser_download_url"], temp_file) + with ZipFile(temp_file, "r") as zip_ref: + zip_ref.extractall(temp_dir) + + for file in list(temp_dir.joinpath("bin").iterdir()): + print(f"Moving {file.name}") + shutil.move(file, DESTINATION_ASSEMBLY_PATH.joinpath(file.name)) + + break + +print("Done.") diff --git a/pyproject.toml b/pyproject.toml index 5c46cac..37751b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,9 @@ dependencies = [ ] [project.optional-dependencies] +tooling = [ + "requests", +] test = [ "pytest", "pytest-cov", @@ -32,6 +35,7 @@ test = [ ] typing = [ "mypy", + "types-requests", "pre-commit" ] diff --git a/src/mars_patcher/misc_patches.py b/src/mars_patcher/misc_patches.py index bf2e7cb..9742dce 100644 --- a/src/mars_patcher/misc_patches.py +++ b/src/mars_patcher/misc_patches.py @@ -1,22 +1,37 @@ import mars_patcher.constants.game_data as gd from mars_patcher.constants.reserved_space import ReservedConstants from mars_patcher.data import get_data_path -from mars_patcher.patching import IpsDecoder +from mars_patcher.patching import BpsDecoder, IpsDecoder from mars_patcher.rom import Rom -def get_patch_path(rom: Rom, filename: str) -> str: +def _get_patch_path(rom: Rom, subfolder: str, filename: str) -> str: dir = f"{rom.game.name}_{rom.region.name}".lower() - return get_data_path("patches", dir, filename) + return get_data_path("patches", dir, subfolder, filename) -def apply_patch_in_data_path(rom: Rom, patch_name: str) -> None: - path = get_patch_path(rom, patch_name) +def _internal_apply_ips_patch(rom: Rom, patch_name: str, subfolder: str) -> None: + path = _get_patch_path(rom, subfolder, patch_name) with open(path, "rb") as f: patch = f.read() IpsDecoder().apply_patch(patch, rom.data) +def apply_patch_in_data_path(rom: Rom, patch_name: str) -> None: + _internal_apply_ips_patch(rom, patch_name, "") + + +def apply_patch_in_asm_path(rom: Rom, patch_name: str) -> None: + _internal_apply_ips_patch(rom, patch_name, "asm") + + +def apply_base_patch(rom: Rom) -> None: + path = _get_patch_path(rom, "asm", "m4rs.bps") + with open(path, "rb") as f: + patch = f.read() + rom.data = BpsDecoder().apply_patch(patch, rom.data) + + def disable_demos(rom: Rom) -> None: # TODO: Move to patch # b 0x8087460 @@ -61,15 +76,15 @@ def change_missile_limit(rom: Rom, limit: int) -> None: def apply_unexplored_map(rom: Rom) -> None: - apply_patch_in_data_path(rom, "unhidden_map.ips") + apply_patch_in_asm_path(rom, "unhidden_map.ips") def apply_pbs_without_bombs(rom: Rom) -> None: - apply_patch_in_data_path(rom, "bombless_pbs.ips") + apply_patch_in_asm_path(rom, "bombless_pbs.ips") def apply_anti_softlock_edits(rom: Rom) -> None: - apply_patch_in_data_path(rom, "anti_softlock.ips") + apply_patch_in_asm_path(rom, "anti_softlock.ips") def apply_reveal_hidden_tiles(rom: Rom) -> None: diff --git a/src/mars_patcher/patcher.py b/src/mars_patcher/patcher.py index 861543a..5d365bd 100644 --- a/src/mars_patcher/patcher.py +++ b/src/mars_patcher/patcher.py @@ -14,6 +14,7 @@ from mars_patcher.minimap import apply_minimap_edits from mars_patcher.misc_patches import ( apply_anti_softlock_edits, + apply_base_patch, apply_pbs_without_bombs, apply_reveal_hidden_tiles, apply_unexplored_map, @@ -66,6 +67,10 @@ def patch( # Load input rom rom = Rom(input_path) + # Apply base asm patch first + apply_base_patch(rom) + + # Softlock edits need to be done early to prevent later patches messing things up. if patch_data.get("AntiSoftlockRoomEdits"): apply_anti_softlock_edits(rom)