Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions worlds/pokemon_emerald/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 2.5.0

### Features

- Changed patch file format to pull base patch from apworld instead of including it in the patch, significantly reducing
patch file size.

# 2.4.1

### Fixes
Expand Down
21 changes: 15 additions & 6 deletions worlds/pokemon_emerald/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import copy
import logging
import os
import pkgutil
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union

from BaseClasses import CollectionState, ItemClassification, MultiWorld, Tutorial, LocationProgressType
Expand All @@ -15,7 +14,8 @@
from worlds.AutoWorld import WebWorld, World

from .client import PokemonEmeraldClient # Unused, but required to register with BizHawkClient
from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, LocationCategory, data as emerald_data
from .data import (LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, LocationCategory, MiscPokemonData,
data as emerald_data)
from .groups import ITEM_GROUPS, LOCATION_GROUPS
from .items import PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification, offset_item_value
from .locations import (PokemonEmeraldLocation, create_location_label_to_id_map, create_locations_by_category,
Expand Down Expand Up @@ -125,16 +125,24 @@ class PokemonEmeraldWorld(World):
blacklisted_opponent_pokemon: Set[int]
hm_requirements: Dict[str, Union[int, List[str]]]
auth: bytes
base_patch_hash: str | None = None

modified_species: Dict[int, SpeciesData]
modified_maps: Dict[str, MapData]
modified_tmhm_moves: List[int]
modified_legendary_encounters: List[int]
modified_legendary_encounters: List[MiscPokemonData]
modified_misc_pokemon: List[MiscPokemonData]
modified_starters: Tuple[int, int, int]
modified_trainers: List[TrainerData]

def __init__(self, multiworld, player):
super(PokemonEmeraldWorld, self).__init__(multiworld, player)
super().__init__(multiworld, player)

if self.base_patch_hash is None:
import hashlib
import pkgutil
self.base_patch_hash = hashlib.sha256(pkgutil.get_data(__name__, "data/base_patch.bsdiff4")).hexdigest()

self.badge_shuffle_info = None
self.hm_shuffle_info = None
self.free_fly_location_id = 0
Expand All @@ -148,6 +156,7 @@ def __init__(self, multiworld, player):
self.modified_starters = emerald_data.starters
self.modified_trainers = []
self.modified_legendary_encounters = []
self.modified_misc_pokemon = []

@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
Expand Down Expand Up @@ -365,7 +374,7 @@ def create_items(self) -> None:
# Filter progression items which shouldn't be shuffled into the itempool.
# Their locations will still exist, but event items will be placed and
# locked at their vanilla locations instead.
filter_categories = set()
filter_categories: set[LocationCategory] = set()

if not self.options.key_items:
filter_categories.add(LocationCategory.KEY)
Expand Down Expand Up @@ -644,7 +653,7 @@ def generate_output(self, output_directory: str) -> None:
randomize_starters(self)

patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.player_name)
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4"))
patch.base_patch_hash = self.base_patch_hash
write_tokens(self, patch)

del self.modified_trainers
Expand Down
2 changes: 1 addition & 1 deletion worlds/pokemon_emerald/archipelago.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"game": "Pokemon Emerald",
"world_version": "2.4.1",
"world_version": "2.5.0",
"minimum_ap_version": "0.6.1",
"authors": ["Zunawe"]
}
40 changes: 36 additions & 4 deletions worlds/pokemon_emerald/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
Classes and functions related to creating a ROM patch
"""
import copy
import os
import hashlib
import pkgutil
import struct
from typing import TYPE_CHECKING, Dict, List, Tuple
from typing import TYPE_CHECKING, Dict, List, Tuple, Any

from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
from settings import get_settings

from .data import TrainerPokemonDataTypeEnum, BASE_OFFSET, data
Expand Down Expand Up @@ -100,12 +101,27 @@ class PokemonEmeraldProcedurePatch(APProcedurePatch, APTokenMixin):
hash = "605b89b67018abcea91e693a4dd25be3"
patch_file_ending = ".apemerald"
result_file_ending = ".gba"
base_patch_hash: str
apworld_version: str

procedure = [
("apply_bsdiff4", ["base_patch.bsdiff4"]),
("apply_base_patch", []),
("apply_tokens", ["token_data.bin"])
]

def get_manifest(self) -> Dict[str, Any]:
from . import PokemonEmeraldWorld
manifest = super().get_manifest()
manifest["apworld_version"] = PokemonEmeraldWorld.world_version.as_simple_string()
manifest["base_patch_hash"] = self.base_patch_hash
return manifest

def read_contents(self, opened_zipfile) -> Dict[str, Any]:
manifest = super().read_contents(opened_zipfile)
self.apworld_version = manifest.get("apworld_version", "<2.5.0")
self.base_patch_hash = manifest.get("base_patch_hash", "")
return manifest

@classmethod
def get_source_data(cls) -> bytes:
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
Expand All @@ -114,6 +130,22 @@ def get_source_data(cls) -> bytes:
return base_rom_bytes


class PokemonEmeraldPatchExtensions(APPatchExtension):
game = "Pokemon Emerald"

@staticmethod
def apply_base_patch(caller: PokemonEmeraldProcedurePatch, rom: bytes) -> bytes:
base_patch = pkgutil.get_data(__name__, "data/base_patch.bsdiff4")
if hashlib.sha256(base_patch).hexdigest() != caller.base_patch_hash:
from . import PokemonEmeraldWorld
raise AssertionError(f"This patch file requires a different version of the Pokemon Emerald APWorld. It was "
f"made with APWorld version {caller.apworld_version}. You're using APWorld version "
f"{PokemonEmeraldWorld.world_version.as_simple_string()}.")

import bsdiff4
return bsdiff4.patch(rom, base_patch)


def write_tokens(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
# TODO: Remove when the base patch is updated to include this change
# Moves an NPC to avoid overlapping people during trainersanity
Expand Down
Loading