From 920b6b4095472b2d02f17f7c5dc5bc63474693bb Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:24:06 -0800 Subject: [PATCH 1/7] Modify tilesets for minor locations --- src/mars_patcher/constants/game_data.py | 28 +++ src/mars_patcher/convert_array.py | 23 ++ src/mars_patcher/mf/item_patcher.py | 2 +- src/mars_patcher/palette.py | 9 +- src/mars_patcher/text.py | 7 +- src/mars_patcher/tilemap.py | 44 ++++ src/mars_patcher/tileset.py | 41 +++- src/mars_patcher/zm/constants/items.py | 63 +++++ .../zm/constants/reserved_space.py | 8 +- .../zm/data/item_palettes/bombs.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/charge_beam.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/gravity_suit.pal | 1 + .../zm/data/item_palettes/hi_jump.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/ice_beam.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/long_beam.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/morph_ball.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/plasma_beam.pal | 1 + .../zm/data/item_palettes/power_grip.pal | 1 + .../zm/data/item_palettes/screw_attack.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/space_jump.pal | 1 + .../zm/data/item_palettes/speed_booster.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/tank.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/varia_suit.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/wave_beam.pal | Bin 0 -> 32 bytes .../zm/data/item_palettes/ziplines.pal | Bin 0 -> 32 bytes src/mars_patcher/zm/item_patcher.py | 216 +++++++++++++++++- src/mars_patcher/zm/locations.py | 8 +- src/mars_patcher/zm/patcher.py | 8 + 28 files changed, 433 insertions(+), 28 deletions(-) create mode 100644 src/mars_patcher/convert_array.py create mode 100644 src/mars_patcher/tilemap.py create mode 100644 src/mars_patcher/zm/data/item_palettes/bombs.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/charge_beam.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/gravity_suit.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/hi_jump.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/ice_beam.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/long_beam.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/morph_ball.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/plasma_beam.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/power_grip.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/screw_attack.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/space_jump.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/speed_booster.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/tank.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/varia_suit.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/wave_beam.pal create mode 100644 src/mars_patcher/zm/data/item_palettes/ziplines.pal diff --git a/src/mars_patcher/constants/game_data.py b/src/mars_patcher/constants/game_data.py index 3a6a8d4..91d978c 100644 --- a/src/mars_patcher/constants/game_data.py +++ b/src/mars_patcher/constants/game_data.py @@ -45,6 +45,25 @@ def tileset_count(rom: Rom) -> int: raise ValueError(rom.game) +def anim_tileset_entries(rom: Rom) -> int: + """Returns the address of the animated tileset entries.""" + if rom.game == Game.MF: + raise NotImplementedError() + elif rom.game == Game.ZM: + return rom.read_ptr(ReservedPointersZM.ANIM_TILESET_ENTRIES_PTR.value) + + raise ValueError("Rom has unknown game loaded.") + + +def anim_tileset_count(rom: Rom) -> int: + """Returns the number of animated tilesets in the game.""" + if rom.game == Game.MF: + return 0xE + elif rom.game == Game.ZM: + return 0x8 + raise ValueError(rom.game) + + def area_doors_ptrs(rom: Rom) -> int: """Returns the address of the area doors pointers.""" if rom.game == Game.MF: @@ -89,6 +108,15 @@ def area_connections_count(rom: Rom) -> int: raise ValueError("Rom has unknown game loaded.") +def anim_graphics_count(rom: Rom) -> int: + """Returns the number of animated graphics in the game.""" + if rom.game == Game.MF: + return 0x47 + elif rom.game == Game.ZM: + return 0x26 + raise ValueError(rom.game, rom.region) + + def anim_palette_entries(rom: Rom) -> int: """Returns the address of the animated palette entries.""" if rom.game == Game.MF: diff --git a/src/mars_patcher/convert_array.py b/src/mars_patcher/convert_array.py new file mode 100644 index 0000000..20eb7cc --- /dev/null +++ b/src/mars_patcher/convert_array.py @@ -0,0 +1,23 @@ +from mars_patcher.rom import ROM_OFFSET + + +def u8_to_u16(data: bytes | list[int]) -> list[int]: + """Converts a bytes object or list of 8-bit integers to a list of 16-bit integers.""" + assert len(data) % 2 == 0, "Data length must be a multiple of 2" + return [data[i] | (data[i + 1] << 8) for i in range(0, len(data), 2)] + + +def u16_to_u8(data: list[int]) -> bytes: + """Converts a list of 16-bit integers to a bytes object of 8-bit integers.""" + output = bytearray() + for val in data: + output.append(val & 0xFF) + output.append(val >> 8) + return output + + +def ptr_to_u8(val: int) -> bytes: + """Converts a single pointer to a bytes object of 8-bit integers.""" + assert val < ROM_OFFSET, f"Pointer should be less than {ROM_OFFSET:X} but is {val:X}" + val += ROM_OFFSET + return bytes([val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, val >> 24]) diff --git a/src/mars_patcher/mf/item_patcher.py b/src/mars_patcher/mf/item_patcher.py index e6a1fb2..a64e82b 100644 --- a/src/mars_patcher/mf/item_patcher.py +++ b/src/mars_patcher/mf/item_patcher.py @@ -84,7 +84,7 @@ def write_items(self) -> None: if not min_loc.hidden: # Get tilemap tileset = Tileset(rom, room.tileset()) - addr = tileset.rle_tilemap_addr() + addr = tileset.tilemap_addr() # Find tank in tilemap addr += 2 + (TANK_BG1_START * 8) tile = TANK_TILE[tank_slot] diff --git a/src/mars_patcher/palette.py b/src/mars_patcher/palette.py index 20b9cf0..30fd798 100644 --- a/src/mars_patcher/palette.py +++ b/src/mars_patcher/palette.py @@ -2,6 +2,7 @@ import random from mars_patcher.color_spaces import HsvColor, OklabColor, RgbBitSize, RgbColor +from mars_patcher.convert_array import u16_to_u8 from mars_patcher.rom import Rom HUE_VARIATION_RANGE = 180.0 @@ -80,12 +81,8 @@ def rows(self) -> int: return len(self.colors) // 16 def byte_data(self) -> bytes: - arr = bytearray() - for color in self.colors: - val = color.rgb_15() - arr.append(val & 0xFF) - arr.append(val >> 8) - return bytes(arr) + values = [c.rgb_15() for c in self.colors] + return u16_to_u8(values) def write(self, rom: Rom, addr: int) -> None: data = self.byte_data() diff --git a/src/mars_patcher/text.py b/src/mars_patcher/text.py index fbdbfe2..c11f6c3 100644 --- a/src/mars_patcher/text.py +++ b/src/mars_patcher/text.py @@ -3,6 +3,7 @@ from functools import cache from mars_patcher.constants.game_data import character_widths +from mars_patcher.convert_array import u16_to_u8 from mars_patcher.mf.constants.game_data import file_screen_text_ptrs from mars_patcher.mf.data import get_data_path from mars_patcher.rom import Region, Rom @@ -237,11 +238,7 @@ def encode_text( text.append(END) - text_bytes = bytearray() - for val in text: - text_bytes.append(val & 0xFF) - text_bytes.append(val >> 8) - return bytes(text_bytes) + return u16_to_u8(text) def write_seed_hash(rom: Rom, seed_hash: str) -> None: diff --git a/src/mars_patcher/tilemap.py b/src/mars_patcher/tilemap.py new file mode 100644 index 0000000..1ed1fe3 --- /dev/null +++ b/src/mars_patcher/tilemap.py @@ -0,0 +1,44 @@ +from mars_patcher.compress import comp_lz77, decomp_lz77 +from mars_patcher.convert_array import u8_to_u16, u16_to_u8 +from mars_patcher.rom import Rom + + +class Tilemap: + def __init__(self, rom: Rom, ptr: int, compressed: bool): + self.rom = rom + self.pointer = ptr + self.compressed = compressed + # Get data + addr = rom.read_ptr(ptr) + self.data: list[int] = [] + if compressed: + data, self.data_size = decomp_lz77(rom.data, addr) + self.data = u8_to_u16(data) + else: + addr += 2 + while True: + val = rom.read_16(addr) + if val == 0: + if len(self.data) % 4 != 0: + raise ValueError("Tilemap length should be a multiple of 4") + break + if len(self.data) >= 1024 * 4: + raise ValueError("Tilemap is too long") + self.data.append(val) + addr += 2 + self.data_size = len(self.data) * 2 + 4 + + def byte_data(self) -> bytes: + if self.compressed: + data = comp_lz77(u16_to_u8(self.data)) + return bytes(data) + else: + return bytes([2, 0]) + u16_to_u8(self.data) + bytes([0, 0]) + + def write(self, copy: bool) -> None: + data = self.byte_data() + if copy: + self.rom.write_data_with_pointers(data, [self.pointer]) + else: + addr = self.rom.read_ptr(self.pointer) + self.rom.write_repointable_data(addr, self.data_size, data, [self.pointer]) diff --git a/src/mars_patcher/tileset.py b/src/mars_patcher/tileset.py index c70e122..f76d3c9 100644 --- a/src/mars_patcher/tileset.py +++ b/src/mars_patcher/tileset.py @@ -1,11 +1,44 @@ -from mars_patcher.constants.game_data import tileset_entries +from mars_patcher.constants.game_data import anim_tileset_entries, tileset_entries from mars_patcher.rom import Rom +TILESET_SIZE = 0x14 +ANIM_TILESET_SIZE = 0x30 + class Tileset: def __init__(self, rom: Rom, id: int): self.rom = rom - self.addr = tileset_entries(rom) + id * 0x14 + self.addr = tileset_entries(rom) + id * TILESET_SIZE + + def block_bg_gfx_ptr(self) -> int: + return self.addr + + def block_bg_gfx_addr(self) -> int: + return self.rom.read_ptr(self.block_bg_gfx_ptr()) + + def palette_ptr(self) -> int: + return self.addr + 4 + + def palette_addr(self) -> int: + return self.rom.read_ptr(self.palette_ptr()) + + def tiled_bg_gfx_ptr(self) -> int: + return self.addr + 8 + + def tiled_bg_gfx_addr(self) -> int: + return self.rom.read_ptr(self.tiled_bg_gfx_ptr()) + + def tilemap_ptr(self) -> int: + return self.addr + 0xC + + def tilemap_addr(self) -> int: + return self.rom.read_ptr(self.tilemap_ptr()) + + def anim_tileset(self) -> int: + return self.rom.read_8(self.addr + 0x10) + + def anim_tileset_addr(self) -> int: + return anim_tileset_entries(self.rom) + self.anim_tileset() * ANIM_TILESET_SIZE - def rle_tilemap_addr(self) -> int: - return self.rom.read_ptr(self.addr + 0xC) + def anim_palette(self) -> int: + return self.rom.read_8(self.addr + 0x11) diff --git a/src/mars_patcher/zm/constants/items.py b/src/mars_patcher/zm/constants/items.py index aafcc87..3da739b 100644 --- a/src/mars_patcher/zm/constants/items.py +++ b/src/mars_patcher/zm/constants/items.py @@ -1,5 +1,7 @@ from enum import IntEnum, auto +from mars_patcher.zm.data import get_data_path + class MajorSource(IntEnum): LONG_BEAM = 0 @@ -74,6 +76,67 @@ class ItemSprite(IntEnum): SHINY_POWER_BOMB_TANK = auto() +ITEM_TO_SPRITE = { + ItemType.NONE: ItemSprite.EMPTY, + ItemType.ENERGY_TANK: ItemSprite.ENERGY_TANK, + ItemType.MISSILE_TANK: ItemSprite.MISSILE_TANK, + ItemType.SUPER_MISSILE_TANK: ItemSprite.SUPER_MISSILE_TANK, + ItemType.POWER_BOMB_TANK: ItemSprite.POWER_BOMB_TANK, + ItemType.LONG_BEAM: ItemSprite.LONG_BEAM, + ItemType.CHARGE_BEAM: ItemSprite.CHARGE_BEAM, + ItemType.ICE_BEAM: ItemSprite.ICE_BEAM, + ItemType.WAVE_BEAM: ItemSprite.WAVE_BEAM, + ItemType.PLASMA_BEAM: ItemSprite.PLASMA_BEAM, + ItemType.BOMBS: ItemSprite.BOMBS, + ItemType.VARIA_SUIT: ItemSprite.VARIA_SUIT, + ItemType.GRAVITY_SUIT: ItemSprite.GRAVITY_SUIT, + ItemType.MORPH_BALL: ItemSprite.MORPH_BALL, + ItemType.SPEED_BOOSTER: ItemSprite.SPEED_BOOSTER, + ItemType.HI_JUMP: ItemSprite.HI_JUMP, + ItemType.SCREW_ATTACK: ItemSprite.SCREW_ATTACK, + ItemType.SPACE_JUMP: ItemSprite.SPACE_JUMP, + ItemType.POWER_GRIP: ItemSprite.POWER_GRIP, + ItemType.FULLY_POWERED: ItemSprite.FULLY_POWERED, + ItemType.ZIPLINES: ItemSprite.ZIPLINES, +} + + +PALETTE_NAMES = { + ItemSprite.EMPTY: "tank", + ItemSprite.ENERGY_TANK: "tank", + ItemSprite.MISSILE_TANK: "tank", + ItemSprite.SUPER_MISSILE_TANK: "tank", + ItemSprite.POWER_BOMB_TANK: "tank", + ItemSprite.LONG_BEAM: "long_beam", + ItemSprite.CHARGE_BEAM: "charge_beam", + ItemSprite.ICE_BEAM: "ice_beam", + ItemSprite.WAVE_BEAM: "wave_beam", + ItemSprite.PLASMA_BEAM: "plasma_beam", + ItemSprite.BOMBS: "bombs", + ItemSprite.VARIA_SUIT: "varia_suit", + ItemSprite.GRAVITY_SUIT: "gravity_suit", + ItemSprite.MORPH_BALL: "morph_ball", + ItemSprite.SPEED_BOOSTER: "speed_booster", + ItemSprite.HI_JUMP: "hi_jump", + ItemSprite.SCREW_ATTACK: "screw_attack", + ItemSprite.SPACE_JUMP: "space_jump", + ItemSprite.POWER_GRIP: "power_grip", + ItemSprite.FULLY_POWERED: "morph_ball", # TODO: Add palette for this + ItemSprite.ZIPLINES: "ziplines", + ItemSprite.ANONYMOUS: "tank", + # TODO: Add palettes for these + # ItemSprite.SHINY_MISSILE_TANK: "shiny_tank", + # ItemSprite.SHINY_POWER_BOMB_TANK: "shiny_tank", +} + + +def get_sprite_palette(sprite: ItemSprite) -> bytes: + name = PALETTE_NAMES[sprite] + ".pal" + path = get_data_path("item_palettes", name) + with open(path, "rb") as f: + return f.read() + + class ItemJingle(IntEnum): DEFAULT = 0 MINOR = auto() diff --git a/src/mars_patcher/zm/constants/reserved_space.py b/src/mars_patcher/zm/constants/reserved_space.py index 628065a..b19b022 100644 --- a/src/mars_patcher/zm/constants/reserved_space.py +++ b/src/mars_patcher/zm/constants/reserved_space.py @@ -11,8 +11,8 @@ class ReservedConstantsZM: """ # Important addresses: - # 0x760D38 - End of data (U region) - # 0x7C0000 - Patcher data free space + # 0x760D38 - End of vanilla data (U region) + # 0x7B0000 - Patcher data free space # 0x7D0000 - Randomizer data pointers # 0x7D8000 - NES Metroid data @@ -20,7 +20,7 @@ class ReservedConstantsZM: RANDO_POINTERS_ADDR = 0x7D0000 # Address for any additional data that the patcher may need to write - PATCHER_FREE_SPACE_ADDR = 0x7C0000 + PATCHER_FREE_SPACE_ADDR = 0x7B0000 PATCHER_FREE_SPACE_END = RANDO_POINTERS_ADDR @@ -35,6 +35,8 @@ class ReservedPointersZM(IntEnum): """Pointer to the list of tileset entries.""" TILESET_TILEMAP_SIZES_PTR = auto() """Pointer to an array containing the size of each tileset's tilemap.""" + ANIM_TILESET_ENTRIES_PTR = auto() + """Pointer to the list of animated tileset entries.""" MINIMAPS_PTR = auto() """Pointer to a list of pointers to the minimap data for each area.""" AREA_DOORS_PTR = auto() diff --git a/src/mars_patcher/zm/data/item_palettes/bombs.pal b/src/mars_patcher/zm/data/item_palettes/bombs.pal new file mode 100644 index 0000000000000000000000000000000000000000..a3743ed8c2963df67389c96f378443060dda3505 GIT binary patch literal 32 ncmbRz{xVFSSq>HZ=Io*;(g=&O7()`41Ej_m?01Vxtsyu@j3lGQqT#*{z09FJ0SO^3FmVOA+ literal 0 HcmV?d00001 diff --git a/src/mars_patcher/zm/data/item_palettes/morph_ball.pal b/src/mars_patcher/zm/data/item_palettes/morph_ball.pal new file mode 100644 index 0000000000000000000000000000000000000000..62b6be9f22480f488bf44a2ddbc2f7d360d0960c GIT binary patch literal 32 hcmXpp>|^*}|E+#~nRg+3#C-AnO7(*B4B-qg006}J2*dyY literal 0 HcmV?d00001 diff --git a/src/mars_patcher/zm/data/item_palettes/plasma_beam.pal b/src/mars_patcher/zm/data/item_palettes/plasma_beam.pal new file mode 100644 index 0000000..125857a --- /dev/null +++ b/src/mars_patcher/zm/data/item_palettes/plasma_beam.pal @@ -0,0 +1 @@ += scuN1*!?]?]?]?]?]?]?]?] \ No newline at end of file diff --git a/src/mars_patcher/zm/data/item_palettes/power_grip.pal b/src/mars_patcher/zm/data/item_palettes/power_grip.pal new file mode 100644 index 0000000..f1a79a4 --- /dev/null +++ b/src/mars_patcher/zm/data/item_palettes/power_grip.pal @@ -0,0 +1 @@ +AcRJj)gG J`~qT \ No newline at end of file diff --git a/src/mars_patcher/zm/data/item_palettes/screw_attack.pal b/src/mars_patcher/zm/data/item_palettes/screw_attack.pal new file mode 100644 index 0000000000000000000000000000000000000000..484abdd120e7a96ae6f3006499df80854106d419 GIT binary patch literal 32 jcmbQF)+Y16UOYQD&CBtW&VA$kO7(*B48serwn{OAf_*-1FHUFe@?1S_p8gs((_IoXd~^0Ln!Rl>h($ literal 0 HcmV?d00001 diff --git a/src/mars_patcher/zm/data/item_palettes/varia_suit.pal b/src/mars_patcher/zm/data/item_palettes/varia_suit.pal new file mode 100644 index 0000000000000000000000000000000000000000..ec3bc7298d9da65bdaa352782e5d7f44c61b02cf GIT binary patch literal 32 jcmXppWMKGT|E+#~nRg+3#C_xaO7(*B41Ej_m?01V&V~$C literal 0 HcmV?d00001 diff --git a/src/mars_patcher/zm/data/item_palettes/wave_beam.pal b/src/mars_patcher/zm/data/item_palettes/wave_beam.pal new file mode 100644 index 0000000000000000000000000000000000000000..6ac42cc80c65c016c7c24ddd5d006eea61e8a600 GIT binary patch literal 32 jcmbPk%fRrzUcO$w&c4RnfzRc))mBYj)h-$PSO^3FvGodV literal 0 HcmV?d00001 diff --git a/src/mars_patcher/zm/data/item_palettes/ziplines.pal b/src/mars_patcher/zm/data/item_palettes/ziplines.pal new file mode 100644 index 0000000000000000000000000000000000000000..960df64d34a5f944f6defc227429aeeccb13fea0 GIT binary patch literal 32 ocmd-OI>s>HZJy#D1|beEmIv7j+!{0)B int: + # Long beam is the first major + major_num = sprite.value - ItemSprite.LONG_BEAM.value + + # Find blank row in palette (a row is considered blank if all colors + # except the first one are the same) + pal_colors = self.palette.colors + pal_row = -1 + for row in range(1, 14): + index = row * 16 + color = pal_colors[index + 1] + if all(pal_colors[index + i] == color for i in range(2, 16)): + # Get sprite palette and convert to RgbColor + item_pal = get_sprite_palette(sprite) + colors = [RgbColor.from_rgb(rgb, RgbBitSize.Rgb5) for rgb in u8_to_u16(item_pal)] + assert len(colors) == 16, "Item palette should have 16 colors" + pal_colors[index : index + 16] = colors + pal_row = row + 2 + break + if pal_row == -1: + raise ValueError(f"No blank palette row found ({self.id:X})") + + # Find blank entry in animated tileset + anim_gfx_idx = -1 + for i in range(16): + if self.anim_tileset[i * 3] == 0: + anim_gfx_num = anim_graphics_count(self.rom) + major_num + self.anim_tileset[i * 3] = anim_gfx_num + anim_gfx_idx = i + break + if anim_gfx_idx == -1: + raise ValueError("No blank entry found in animated tileset") + + # Find blank tiles in tilemap + tile_val = -1 + block_num = -1 + for i in range(0x4C, 0x50): + offset = i * 4 + if all(self.tilemap.data[offset + t] == 0x40 for t in range(4)): + tile_val = (pal_row << 12) | (anim_gfx_idx * 4) + for t in range(4): + self.tilemap.data[offset + t] = tile_val + t + block_num = i + break + if tile_val == -1 or block_num == -1: + raise ValueError("No blank tiles found in tilemap") + + return block_num + + def write_copy(self, anim_tileset_id: int) -> bytes: + data = bytearray() + # Copy block BG graphics pointer + data += self.rom.read_bytes(self.meta.block_bg_gfx_ptr(), 4) + # Write palette + pal_addr = self.rom.write_data_with_pointers(self.palette.byte_data(), []) + data += ptr_to_u8(pal_addr) + # Copy tiled BG graphics pointer + data += self.rom.read_bytes(self.meta.tiled_bg_gfx_ptr(), 4) + # Write tilemap + tm_addr = self.rom.write_data_with_pointers(self.tilemap.byte_data(), []) + data += ptr_to_u8(tm_addr) + # Write animated tileset number + data.append(anim_tileset_id) + # Copy animated palette number + data.append(self.meta.anim_palette()) + # Padding + data.append(0) + data.append(0) + return data + + class ItemPatcher: """Class for writing item assignments to a ROM.""" @@ -20,14 +120,43 @@ def __init__(self, rom: Rom, settings: LocationSettings): def write_items(self) -> None: rom = self.rom hint_targets_addr = chozo_statue_targets_addr(rom) + seen_messages: dict[str, int] = {} # Handle minor locations # Locations need to be written in order so that binary search works minor_locs = sorted(self.settings.minor_locs, key=lambda x: x.key) minor_loc_addr = minor_locations_addr(rom) + new_tilesets: list[TilesetData] = [] + room_tilesets: dict[tuple[int, int], TilesetData] = {} + orig_tileset_count = tileset_count(rom) + for min_loc in minor_locs: - # TODO: Update tileset and get BG1 value - bg1_val = 0x4A # Use power bomb tank block for now + # Get BG1 block value + sprite = min_loc.item_sprite + if sprite == ItemSprite.DEFAULT: + sprite = ITEM_TO_SPRITE[min_loc.new_item] + match sprite: + case ItemSprite.ENERGY_TANK: + bg1_val = 0x49 + case ItemSprite.MISSILE_TANK: + bg1_val = 0x48 + case ItemSprite.SUPER_MISSILE_TANK: + bg1_val = 0x4B + case ItemSprite.POWER_BOMB_TANK: + bg1_val = 0x4A + case _: + # Update tileset + key = (min_loc.area, min_loc.room) + tileset = room_tilesets.get(key) + if tileset is None: + room = RoomEntry(rom, min_loc.area, min_loc.room) + tileset = TilesetData(rom, room.tileset()) + ts_num = orig_tileset_count + len(new_tilesets) + rom.write_8(room.addr, ts_num) + new_tilesets.append(tileset) + room_tilesets[key] = tileset + bg1_val = tileset.add_major(sprite) + # Overwrite BG1 if not hidden if not min_loc.hidden: room = RoomEntry(rom, min_loc.area, min_loc.room) @@ -39,9 +168,8 @@ def write_items(self) -> None: rom.write_16(minor_loc_addr + 4, bg1_val) rom.write_8(minor_loc_addr + 6, min_loc.new_item.value) rom.write_8(minor_loc_addr + 7, min_loc.item_jingle.value) - # TODO: Handle custom messages - rom.write_32(minor_loc_addr + 8, 0) - rom.write_8(minor_loc_addr + 0xC, min_loc.hint_value) + rom.write_8(minor_loc_addr + 8, min_loc.hint_value) + self.write_item_messages(seen_messages, min_loc.item_messages, minor_loc_addr, False) minor_loc_addr += 0x10 if min_loc.hinted_by != HintLocation.NONE: @@ -52,6 +180,8 @@ def write_items(self) -> None: rom.write_8(target_addr + 7, map_x) rom.write_8(target_addr + 8, map_y) + self.write_new_tilesets(new_tilesets) + # Handle major locations major_locs_addr = major_locations_addr(rom) for maj_loc in self.settings.major_locs: @@ -59,8 +189,7 @@ def write_items(self) -> None: rom.write_8(addr, maj_loc.new_item.value) rom.write_8(addr + 1, maj_loc.item_jingle.value) rom.write_8(addr + 2, maj_loc.hint_value) - # TODO: Handle custom messages - rom.write_32(addr + 4, 0) + self.write_item_messages(seen_messages, maj_loc.item_messages, addr, True) if maj_loc.hinted_by != HintLocation.NONE: target_addr = hint_targets_addr + (maj_loc.hinted_by.value * 0xC) @@ -68,6 +197,79 @@ def write_items(self) -> None: rom.write_8(target_addr + 7, maj_loc.map_x) rom.write_8(target_addr + 8, maj_loc.map_y) + def write_new_tilesets(self, new_tilesets: list[TilesetData]) -> None: + rom = self.rom + + # Get existing tileset data + tileset_addr = tileset_entries(rom) + orig_tileset_size = tileset_count(rom) * TILESET_SIZE + tileset_data = rom.read_bytes(tileset_addr, orig_tileset_size) + + # Get existing animated tileset data + anim_tileset_addr = anim_tileset_entries(rom) + orig_anim_tileset_count = anim_tileset_count(rom) + orig_anim_tileset_size = orig_anim_tileset_count * ANIM_TILESET_SIZE + anim_tileset_data = rom.read_bytes(anim_tileset_addr, orig_anim_tileset_size) + + # Append data for each new tileset and animated tileset + for i, tileset in enumerate(new_tilesets): + tileset_data += tileset.write_copy(orig_anim_tileset_count + i) + anim_tileset_data += tileset.anim_tileset + + # Write data to ROM and repoint + rom.write_repointable_data( + tileset_addr, + orig_tileset_size, + tileset_data, + [ReservedPointersZM.TILESET_ENTRIES_PTR.value], + ) + rom.write_repointable_data( + anim_tileset_addr, + orig_anim_tileset_size, + anim_tileset_data, + [ReservedPointersZM.ANIM_TILESET_ENTRIES_PTR.value], + ) + + def write_item_messages( + self, + seen_messages: dict[str, int], + messages: ItemMessages | None, + loc_addr: int, + is_major: bool, + ) -> None: + rom = self.rom + id_offset = 3 if is_major else 9 + custom_offset = 4 if is_major else 0xC + + if messages is None: + rom.write_8(loc_addr + id_offset, 0xFF) + rom.write_32(loc_addr + custom_offset, 0) # NULL + elif messages.kind == ItemMessagesKind.MESSAGE_ID: + rom.write_8(loc_addr + id_offset, messages.message_id) + rom.write_32(loc_addr + custom_offset, 0) # NULL + elif messages.kind == ItemMessagesKind.CUSTOM_MESSAGE: + # Reserve space for pointers for each language + table_addr = rom.reserve_free_space(len(Language) * 4) + for lang in Language: + # English is required to be set - use English as the fallback value + msg_text = ( + messages.item_messages[lang] + if lang in messages.item_messages + else messages.item_messages[Language.ENGLISH] + ) + text_ptr = table_addr + (lang.value * 4) + # Check if text has been seen before + text_addr = seen_messages.get(msg_text) + if text_addr is None: + encoded_text = encode_text( + rom, MessageType.TWO_LINE, msg_text, centered=messages.centered + ) + text_addr = rom.write_data_with_pointers(encoded_text, [text_ptr]) + seen_messages[msg_text] = text_addr + rom.write_ptr(text_ptr, text_addr) + rom.write_8(loc_addr + id_offset, 0xFF) + rom.write_ptr(loc_addr + custom_offset, table_addr) + def set_tank_increments(rom: Rom, data: MarsschemazmTankIncrements) -> None: addr = tank_increase_amounts_addr(rom) diff --git a/src/mars_patcher/zm/locations.py b/src/mars_patcher/zm/locations.py index bad0d5e..0fc5621 100644 --- a/src/mars_patcher/zm/locations.py +++ b/src/mars_patcher/zm/locations.py @@ -180,15 +180,19 @@ def set_location_data(cls, loc_obj: Location, loc_entry: MarsSchemaZmLocation) - """Sets item, item sprite, custom message (if any), jingle, and hint on a major or minor location.""" loc_obj.new_item = ItemType[loc_entry["item"]] + if "item_sprite" in loc_entry: loc_obj.item_sprite = ItemSprite[loc_entry["item_sprite"]] - # if "ItemMessages" in loc_entry: - # loc_obj.item_messages = ItemMessages.from_json(loc_entry["ItemMessages"]) + if "jingle" in loc_entry: loc_obj.item_jingle = ItemJingle[loc_entry["jingle"]] else: loc_obj.item_jingle = ItemJingle.DEFAULT + if "hinted_by" in loc_entry: loc_obj.hinted_by = HintLocation[loc_entry["hinted_by"]] else: loc_obj.hinted_by = HintLocation.NONE + + if "ItemMessages" in loc_entry: + loc_obj.item_messages = ItemMessages.from_json(loc_entry["ItemMessages"]) diff --git a/src/mars_patcher/zm/patcher.py b/src/mars_patcher/zm/patcher.py index 03dec62..22f6a85 100644 --- a/src/mars_patcher/zm/patcher.py +++ b/src/mars_patcher/zm/patcher.py @@ -5,6 +5,7 @@ from mars_patcher.sounds import set_sounds from mars_patcher.zm.auto_generated_types import MarsSchemaZM from mars_patcher.zm.constants.game_data import skip_door_transitions_addr +from mars_patcher.zm.constants.reserved_space import ReservedConstantsZM from mars_patcher.zm.item_patcher import ItemPatcher, set_tank_increments from mars_patcher.zm.locations import LocationSettings @@ -133,5 +134,12 @@ def patch_zm( # status_update("Writing title screen text...", -1) # write_title_text(rom, title_screen_text) + free_space_size = ( + ReservedConstantsZM.PATCHER_FREE_SPACE_END - ReservedConstantsZM.PATCHER_FREE_SPACE_ADDR + ) + free_space_used = rom.free_space_addr - ReservedConstantsZM.PATCHER_FREE_SPACE_ADDR + percent = free_space_used / free_space_size + print(f"Free space used: {free_space_used:X}/{free_space_size:X} ({percent:.2%})") + rom.save(output_path) status_update(f"Output written to {output_path}", -1) From 0322f7a4a9fab9cd55c71ac5a06d264c83a0c82c Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:54:04 -0800 Subject: [PATCH 2/7] Fix custom message types --- src/mars_patcher/zm/auto_generated_types.py | 30 +++++++------- src/mars_patcher/zm/data/schema.json | 46 ++++++++++----------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/mars_patcher/zm/auto_generated_types.py b/src/mars_patcher/zm/auto_generated_types.py index 5aebd8f..69b5e51 100644 --- a/src/mars_patcher/zm/auto_generated_types.py +++ b/src/mars_patcher/zm/auto_generated_types.py @@ -137,13 +137,13 @@ 'TOURIAN_TO_CRATERIA' ] ValidLanguages = typ.Literal[ - 'JAPANESE_KANJI', - 'JAPANESE_HIRAGANA', - 'ENGLISH', - 'GERMAN', - 'FRENCH', - 'ITALIAN', - 'SPANISH' + 'JapaneseKanji', + 'JapaneseHiragana', + 'English', + 'German', + 'French', + 'Italian', + 'Spanish' ] Validmusictracks = typ.Literal[ 'BRINSTAR', @@ -213,15 +213,15 @@ MessageLanguages: typ.TypeAlias = dict[ValidLanguages, str] class ItemMessages(typ.TypedDict, total=False): - kind: typ.Required[ItemMessagesKind] - languages: MessageLanguages - centered: bool = True - message_id: typ.Annotated[int, '0 <= value <= 56'] + Kind: typ.Required[ItemMessagesKind] + Languages: MessageLanguages + Centered: bool = True + MessageID: typ.Annotated[int, '0 <= value <= 56'] """The Message ID, will display one of the predefined messages in the ROM""" ItemMessagesKind = typ.Literal[ - 'CUSTOM_MESSAGE', - 'MESSAGE_ID' + 'CustomMessage', + 'MessageID' ] Jingle = typ.Literal[ 'DEFAULT', @@ -266,7 +266,7 @@ class MarsschemazmLocationsMajorLocationsItem(typ.TypedDict, total=False): item_sprite: ValidItemSprites """Valid graphics for item tanks/sprites.""" - item_messages: ItemMessages + ItemMessages: ItemMessages jingle: Jingle """The sound that plays when an item is collected""" @@ -293,7 +293,7 @@ class MarsschemazmLocationsMinorLocationsItem(typ.TypedDict): item_sprite: typ.NotRequired[ValidItemSprites] """Valid graphics for item tanks/sprites.""" - item_messages: typ.NotRequired[ItemMessages] + ItemMessages: typ.NotRequired[ItemMessages] jingle: typ.NotRequired[Jingle] """The sound that plays when an item is collected""" diff --git a/src/mars_patcher/zm/data/schema.json b/src/mars_patcher/zm/data/schema.json index 5511f32..0bed6c3 100644 --- a/src/mars_patcher/zm/data/schema.json +++ b/src/mars_patcher/zm/data/schema.json @@ -31,7 +31,7 @@ "item_sprite": { "$ref": "#/$defs/valid_item_sprites" }, - "item_messages": { + "ItemMessages": { "$ref": "#/$defs/item_messages" }, "jingle": { @@ -79,7 +79,7 @@ "item_sprite": { "$ref": "#/$defs/valid_item_sprites" }, - "item_messages": { + "ItemMessages": { "$ref": "#/$defs/item_messages" }, "jingle": { @@ -784,13 +784,13 @@ "type": "string", "description": "Valid languages supported by the game.", "enum": [ - "JAPANESE_KANJI", - "JAPANESE_HIRAGANA", - "ENGLISH", - "GERMAN", - "FRENCH", - "ITALIAN", - "SPANISH" + "JapaneseKanji", + "JapaneseHiragana", + "English", + "German", + "French", + "Italian", + "Spanish" ] }, "ValidMusicTracks": { @@ -877,7 +877,7 @@ "$ref": "#/$defs/valid_languages" }, "required": [ - "ENGLISH" + "English" ], "additionalProperties": { "type": "string", @@ -887,44 +887,44 @@ "item_messages": { "type": "object", "properties": { - "kind": { + "Kind": { "$ref": "#/$defs/item_messages_kind" } }, "required": [ - "kind" + "Kind" ], "if": { "properties": { - "kind": { - "const": "CUSTOM_MESSAGE" + "Kind": { + "const": "CustomMessage" } } }, "then": { "properties": { - "kind": { + "Kind": { "$ref": "#/$defs/item_messages_kind" }, - "languages": { + "Languages": { "$ref": "#/$defs/message_languages" }, - "centered": { + "Centered": { "type": "boolean", "default": true } }, "required": [ - "languages" + "Languages" ], "additionalProperties": false }, "else": { "properties": { - "kind": { + "Kind": { "$ref": "#/$defs/item_messages_kind" }, - "message_id": { + "MessageID": { "type": "integer", "minimum": 0, "maximum": 56, @@ -932,7 +932,7 @@ } }, "required": [ - "message_id" + "MessageID" ], "additionalProperties": false } @@ -940,8 +940,8 @@ "item_messages_kind": { "type": "string", "enum": [ - "CUSTOM_MESSAGE", - "MESSAGE_ID" + "CustomMessage", + "MessageID" ] }, "jingle": { From 56e5ba033705664ba0b5539ae9fb66a7bba239d3 Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:57:28 -0800 Subject: [PATCH 3/7] [ZM] Add more documentation --- src/mars_patcher/zm/constants/game_data.py | 4 ---- src/mars_patcher/zm/constants/items.py | 7 +++++-- .../zm/constants/reserved_space.py | 2 -- src/mars_patcher/zm/item_patcher.py | 21 ++++++++++++++----- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/mars_patcher/zm/constants/game_data.py b/src/mars_patcher/zm/constants/game_data.py index c635bf0..3c38d35 100644 --- a/src/mars_patcher/zm/constants/game_data.py +++ b/src/mars_patcher/zm/constants/game_data.py @@ -2,10 +2,6 @@ from mars_patcher.zm.constants.reserved_space import ReservedPointersZM -def tileset_tilemap_sizes_addr(rom: Rom) -> int: - return rom.read_ptr(ReservedPointersZM.TILESET_TILEMAP_SIZES_PTR.value) - - def chozo_statue_targets_addr(rom: Rom) -> int: return rom.read_ptr(ReservedPointersZM.CHOZO_STATUE_TARGETS_PTR.value) diff --git a/src/mars_patcher/zm/constants/items.py b/src/mars_patcher/zm/constants/items.py index 3da739b..de378b1 100644 --- a/src/mars_patcher/zm/constants/items.py +++ b/src/mars_patcher/zm/constants/items.py @@ -50,11 +50,14 @@ class ItemType(IntEnum): class ItemSprite(IntEnum): DEFAULT = auto() - EMPTY = auto() + # These are already part of every tileset ENERGY_TANK = auto() MISSILE_TANK = auto() SUPER_MISSILE_TANK = auto() POWER_BOMB_TANK = auto() + # These need to be added to tilesets. The order here should be kept in sync + # with AnimatedGfxId in constants/animated_graphics.h + EMPTY = auto() LONG_BEAM = auto() CHARGE_BEAM = auto() ICE_BEAM = auto() @@ -102,11 +105,11 @@ class ItemSprite(IntEnum): PALETTE_NAMES = { - ItemSprite.EMPTY: "tank", ItemSprite.ENERGY_TANK: "tank", ItemSprite.MISSILE_TANK: "tank", ItemSprite.SUPER_MISSILE_TANK: "tank", ItemSprite.POWER_BOMB_TANK: "tank", + ItemSprite.EMPTY: "tank", ItemSprite.LONG_BEAM: "long_beam", ItemSprite.CHARGE_BEAM: "charge_beam", ItemSprite.ICE_BEAM: "ice_beam", diff --git a/src/mars_patcher/zm/constants/reserved_space.py b/src/mars_patcher/zm/constants/reserved_space.py index b19b022..b783821 100644 --- a/src/mars_patcher/zm/constants/reserved_space.py +++ b/src/mars_patcher/zm/constants/reserved_space.py @@ -33,8 +33,6 @@ class ReservedPointersZM(IntEnum): """Pointer to the list of pointers to the room entries for each area.""" TILESET_ENTRIES_PTR = auto() """Pointer to the list of tileset entries.""" - TILESET_TILEMAP_SIZES_PTR = auto() - """Pointer to an array containing the size of each tileset's tilemap.""" ANIM_TILESET_ENTRIES_PTR = auto() """Pointer to the list of animated tileset entries.""" MINIMAPS_PTR = auto() diff --git a/src/mars_patcher/zm/item_patcher.py b/src/mars_patcher/zm/item_patcher.py index 2c21158..f00f845 100644 --- a/src/mars_patcher/zm/item_patcher.py +++ b/src/mars_patcher/zm/item_patcher.py @@ -27,6 +27,8 @@ class TilesetData: + """Class for creating copies of tilesets with added item graphics.""" + def __init__(self, rom: Rom, id: int): self.rom = rom self.id = id @@ -39,9 +41,15 @@ def __init__(self, rom: Rom, id: int): ats_addr = self.meta.anim_tileset_addr() self.anim_tileset = rom.read_bytes(ats_addr, 0x30) - def add_major(self, sprite: ItemSprite) -> int: - # Long beam is the first major - major_num = sprite.value - ItemSprite.LONG_BEAM.value + def add_item_graphics(self, sprite: ItemSprite) -> int: + """Adds item graphics to the tileset and returns the block number. This function + should be called for anything that isn't one of the 4 original tank types.""" + assert sprite not in { + ItemSprite.ENERGY_TANK, + ItemSprite.MISSILE_TANK, + ItemSprite.SUPER_MISSILE_TANK, + ItemSprite.POWER_BOMB_TANK, + }, "This function should not be called for tanks" # Find blank row in palette (a row is considered blank if all colors # except the first one are the same) @@ -65,7 +73,8 @@ def add_major(self, sprite: ItemSprite) -> int: anim_gfx_idx = -1 for i in range(16): if self.anim_tileset[i * 3] == 0: - anim_gfx_num = anim_graphics_count(self.rom) + major_num + offset = sprite.value - ItemSprite.EMPTY.value + anim_gfx_num = anim_graphics_count(self.rom) + offset self.anim_tileset[i * 3] = anim_gfx_num anim_gfx_idx = i break @@ -89,6 +98,8 @@ def add_major(self, sprite: ItemSprite) -> int: return block_num def write_copy(self, anim_tileset_id: int) -> bytes: + """Writes the palette and tilemap to a new location, and returns the + data for a new tileset entry.""" data = bytearray() # Copy block BG graphics pointer data += self.rom.read_bytes(self.meta.block_bg_gfx_ptr(), 4) @@ -155,7 +166,7 @@ def write_items(self) -> None: rom.write_8(room.addr, ts_num) new_tilesets.append(tileset) room_tilesets[key] = tileset - bg1_val = tileset.add_major(sprite) + bg1_val = tileset.add_item_graphics(sprite) # Overwrite BG1 if not hidden if not min_loc.hidden: From 1bb701a880d29588a635d64eb6a6aeca510f66b4 Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:57:52 -0800 Subject: [PATCH 4/7] Add ZM char map --- src/mars_patcher/text.py | 19 +- src/mars_patcher/zm/data/char_map_zm.json | 2058 +++++++++++++++++++++ 2 files changed, 2071 insertions(+), 6 deletions(-) create mode 100644 src/mars_patcher/zm/data/char_map_zm.json diff --git a/src/mars_patcher/text.py b/src/mars_patcher/text.py index c11f6c3..e84a6c3 100644 --- a/src/mars_patcher/text.py +++ b/src/mars_patcher/text.py @@ -5,8 +5,9 @@ from mars_patcher.constants.game_data import character_widths from mars_patcher.convert_array import u16_to_u8 from mars_patcher.mf.constants.game_data import file_screen_text_ptrs -from mars_patcher.mf.data import get_data_path -from mars_patcher.rom import Region, Rom +from mars_patcher.mf.data import get_data_path as get_data_path_mf +from mars_patcher.rom import Rom +from mars_patcher.zm.data import get_data_path as get_data_path_zm SPACE_CHAR = 0x40 SPACE_TAG = 0x8000 @@ -57,13 +58,18 @@ class MessageType(Enum): @cache -def get_char_map(region: Region) -> dict[str, int]: - path = get_data_path("char_map_mf.json") +def get_char_map(rom: Rom) -> dict[str, int]: + if rom.is_mf(): + path = get_data_path_mf("char_map_mf.json") + elif rom.is_zm(): + path = get_data_path_zm("char_map_zm.json") + else: + raise ValueError(rom.game) with open(path, encoding="utf-8") as f: sections = json.load(f) char_map: dict[str, int] = {} for section in sections: - if region.name in section["regions"]: + if rom.region.name in section["regions"]: char_map.update(section["chars"]) char_map["\n"] = NEWLINE return char_map @@ -119,7 +125,7 @@ def encode_text( max_width: int = MAX_LINE_WIDTH, centered: bool = False, ) -> bytes: - char_map = get_char_map(rom.region) + char_map = get_char_map(rom) char_widths_addr = character_widths(rom) text: list[int] = [] line_width = 0 @@ -196,6 +202,7 @@ def encode_text( if message_type == MessageType.ONE_LINE: raise ValueError(f'String does not fit on one line:\n"{string}"') if width_since_break > max_width: + print(f"{char_widths_addr:X}") raise ValueError(f'Word does not fit on one line:\n"{string}"') line_width = width_since_break line_number += 1 diff --git a/src/mars_patcher/zm/data/char_map_zm.json b/src/mars_patcher/zm/data/char_map_zm.json new file mode 100644 index 0000000..5e95ede --- /dev/null +++ b/src/mars_patcher/zm/data/char_map_zm.json @@ -0,0 +1,2058 @@ +[ + { + "regions": [ + "U", + "J", + "E", + "C" + ], + "chars": { + " ": 64, + "!": 65, + "”": 66, + "#": 67, + "$": 68, + "%": 69, + "&": 70, + "'": 71, + "(": 72, + ")": 73, + "*": 74, + "+": 75, + ",": 76, + "-": 77, + ".": 78, + "/": 79, + "0": 80, + "1": 81, + "2": 82, + "3": 83, + "4": 84, + "5": 85, + "6": 86, + "7": 87, + "8": 88, + "9": 89, + ":": 90, + ";": 91, + "□": 92, + "=": 93, + "[extra_1]": 94, + "?": 95, + "A": 129, + "B": 130, + "C": 131, + "D": 132, + "E": 133, + "F": 134, + "G": 135, + "H": 136, + "I": 137, + "J": 138, + "K": 139, + "L": 140, + "M": 141, + "N": 142, + "O": 143, + "P": 144, + "Q": 145, + "R": 146, + "S": 147, + "T": 148, + "U": 149, + "V": 150, + "W": 151, + "X": 152, + "Y": 153, + "Z": 154, + "[": 155, + "¥": 156, + "]": 157, + "a": 193, + "b": 194, + "c": 195, + "d": 196, + "e": 197, + "f": 198, + "g": 199, + "h": 200, + "i": 201, + "j": 202, + "k": 203, + "l": 204, + "m": 205, + "n": 206, + "o": 207, + "p": 208, + "q": 209, + "r": 210, + "s": 211, + "t": 212, + "u": 213, + "v": 214, + "w": 215, + "x": 216, + "y": 217, + "z": 218, + "{": 219, + "|": 220, + "}": 221, + "[DEL]": 223, + "[alt_0]": 272, + "[alt_1]": 273, + "[alt_2]": 274, + "[alt_3]": 275, + "[alt_4]": 276, + "[alt_5]": 277, + "[alt_6]": 278, + "[alt_7]": 279, + "[alt_8]": 280, + "[alt_9]": 281, + "。": 321, + "「": 322, + "」": 323, + "、": 324, + "・": 325, + "ヲ": 326, + "ァ": 327, + "ィ": 328, + "ゥ": 329, + "ェ": 330, + "ォ": 331, + "ャ": 332, + "ュ": 333, + "ョ": 334, + "ッ": 335, + "ー": 336, + "ア": 337, + "イ": 338, + "ウ": 339, + "エ": 340, + "オ": 341, + "カ": 342, + "キ": 343, + "ク": 344, + "ケ": 345, + "コ": 346, + "サ": 347, + "シ": 348, + "ス": 349, + "セ": 350, + "ソ": 351, + "タ": 384, + "チ": 385, + "ツ": 386, + "テ": 387, + "ト": 388, + "ナ": 389, + "ニ": 390, + "ヌ": 391, + "ネ": 392, + "ノ": 393, + "ハ": 394, + "ヒ": 395, + "フ": 396, + "ヘ": 397, + "ホ": 398, + "マ": 399, + "ミ": 400, + "ム": 401, + "メ": 402, + "モ": 403, + "ヤ": 404, + "ユ": 405, + "ヨ": 406, + "ラ": 407, + "リ": 408, + "ル": 409, + "レ": 410, + "ロ": 411, + "ワ": 412, + "ン": 413, + "゛": 414, + "゜": 415, + "ダ": 448, + "ヂ": 449, + "ヅ": 450, + "デ": 451, + "ド": 452, + "バ": 458, + "ビ": 459, + "ブ": 460, + "ベ": 461, + "ボ": 462, + "パ": 463, + "ピ": 464, + "プ": 465, + "ペ": 466, + "ポ": 467, + "ガ": 470, + "ギ": 471, + "グ": 472, + "ゲ": 473, + "ゴ": 474, + "ザ": 475, + "ジ": 476, + "ズ": 477, + "ゼ": 478, + "ゾ": 479, + "を": 518, + "ぁ": 519, + "ぃ": 520, + "ぅ": 521, + "ぇ": 522, + "ぉ": 523, + "ゃ": 524, + "ゅ": 525, + "ょ": 526, + "っ": 527, + "あ": 529, + "い": 530, + "う": 531, + "え": 532, + "お": 533, + "か": 534, + "き": 535, + "く": 536, + "け": 537, + "こ": 538, + "さ": 539, + "し": 540, + "す": 541, + "せ": 542, + "そ": 543, + "た": 576, + "ち": 577, + "つ": 578, + "て": 579, + "と": 580, + "な": 581, + "に": 582, + "ぬ": 583, + "ね": 584, + "の": 585, + "は": 586, + "ひ": 587, + "ふ": 588, + "へ": 589, + "ほ": 590, + "ま": 591, + "み": 592, + "む": 593, + "め": 594, + "も": 595, + "や": 596, + "ゆ": 597, + "よ": 598, + "ら": 599, + "り": 600, + "る": 601, + "れ": 602, + "ろ": 603, + "わ": 604, + "ん": 605, + "だ": 640, + "ぢ": 641, + "づ": 642, + "で": 643, + "ど": 644, + "ば": 650, + "び": 651, + "ぶ": 652, + "べ": 653, + "ぼ": 654, + "ぱ": 655, + "ぴ": 656, + "ぷ": 657, + "ぺ": 658, + "ぽ": 659, + "が": 662, + "ぎ": 663, + "ぐ": 664, + "げ": 665, + "ご": 666, + "ざ": 667, + "じ": 668, + "ず": 669, + "ぜ": 670, + "ぞ": 671, + "~": 707, + "…": 708, + "『": 709, + "』": 710, + "℃": 711, + "[Select_button]": 713, + "[Select_button_2]": 714, + "[Select_button_3]": 715, + "[Select_button_4]": 716, + "〈": 718, + "〉": 719, + "[L_box_left]": 722, + "[L_box_right]": 723, + "[R_box_left]": 724, + "[R_box_right]": 725, + "[X_1]": 727, + "[X_2]": 728, + "[X_3]": 729, + "[X_4]": 730, + "[X_5]": 731, + "[X_6]": 732, + "[box_top_left]": 733, + "[box_top_middle]": 734, + "[box_top_right]": 735, + "[Up_button]": 768, + "[Up_button_2]": 769, + "[Down_button]": 770, + "[Down_button_2]": 771, + "[Left_button]": 772, + "[Left_button_2]": 773, + "[Right_button]": 774, + "[Right_button_2]": 775, + "[A_button]": 776, + "[A_button_2]": 777, + "[B_button]": 778, + "[B_button_2]": 779, + "[L_button]": 780, + "[L_button_2]": 781, + "[R_button]": 782, + "[R_button_2]": 783, + "„": 784, + "‟": 785, + "[sup_o]": 786, + "[sup_a]": 787, + "[sup_er]": 788, + "[sup_re]": 789, + "[sup_e]": 790, + "œ": 791, + "Œ": 792, + "[box_bottom_left]": 797, + "[box_bottom_middle]": 798, + "[box_bottom_right]": 799, + "¡": 833, + "¢": 834, + "£": 835, + "¤": 836, + "[currency_yen]": 837, + "¦": 838, + "§": 839, + "¨": 840, + "©": 841, + "ª": 842, + "«": 843, + "¬": 844, + "[SHY]": 845, + "®": 846, + "¯": 847, + "°": 848, + "±": 849, + "²": 850, + "³": 851, + "´": 852, + "µ": 853, + "¶": 854, + "·": 855, + "¸": 856, + "¹": 857, + "º": 858, + "»": 859, + "¼": 860, + "½": 861, + "¾": 862, + "¿": 863, + "À": 896, + "Á": 897, + "Â": 898, + "Ã": 899, + "Ä": 900, + "Å": 901, + "Æ": 902, + "Ç": 903, + "È": 904, + "É": 905, + "Ê": 906, + "Ë": 907, + "Ì": 908, + "Í": 909, + "Î": 910, + "Ï": 911, + "Ð": 912, + "Ñ": 913, + "Ò": 914, + "Ó": 915, + "Ô": 916, + "Õ": 917, + "Ö": 918, + "×": 919, + "Ø": 920, + "Ù": 921, + "Ú": 922, + "Û": 923, + "Ũ": 924, + "Ý": 925, + "Þ": 926, + "ß": 927, + "à": 960, + "á": 961, + "â": 962, + "ã": 963, + "ä": 964, + "å": 965, + "æ": 966, + "ç": 967, + "è": 968, + "é": 969, + "ê": 970, + "ë": 971, + "ì": 972, + "í": 973, + "î": 974, + "ï": 975, + "ð": 976, + "ñ": 977, + "ò": 978, + "ó": 979, + "ô": 980, + "õ": 981, + "ö": 982, + "÷": 983, + "ø": 984, + "ù": 985, + "ú": 986, + "û": 987, + "ũ": 988, + "ý": 989, + "þ": 990, + "ÿ": 991, + " ": 1024, + "!": 1025, + "〞": 1026, + "#": 1027, + "$": 1028, + "%": 1029, + "&": 1030, + "'": 1031, + "(": 1032, + ")": 1033, + "*": 1034, + "+": 1035, + ",": 1036, + "-": 1037, + ".": 1038, + "/": 1039, + "0": 1040, + "1": 1041, + "2": 1042, + "3": 1043, + "4": 1044, + "5": 1045, + "6": 1046, + "7": 1047, + "8": 1048, + "9": 1049, + ":": 1050, + ";": 1051, + "Ü": 1052, + "ü": 1053, + "〝": 1054, + "?": 1055, + "A": 1089, + "B": 1090, + "C": 1091, + "D": 1092, + "E": 1093, + "F": 1094, + "G": 1095, + "H": 1096, + "I": 1097, + "J": 1098, + "K": 1099, + "L": 1100, + "M": 1101, + "N": 1102, + "O": 1103, + "P": 1104, + "Q": 1105, + "R": 1106, + "S": 1107, + "T": 1108, + "U": 1109, + "V": 1110, + "W": 1111, + "X": 1112, + "Y": 1113, + "Z": 1114, + "[": 1115, + "¥": 1116, + "]": 1117, + "a": 1153, + "b": 1154, + "c": 1155, + "d": 1156, + "e": 1157, + "f": 1158, + "g": 1159, + "h": 1160, + "i": 1161, + "j": 1162, + "k": 1163, + "l": 1164, + "m": 1165, + "n": 1166, + "o": 1167, + "p": 1168, + "q": 1169, + "r": 1170, + "s": 1171, + "t": 1172, + "u": 1173, + "v": 1174, + "w": 1175, + "x": 1176, + "y": 1177, + "z": 1178, + "{": 1179, + "|": 1180, + "}": 1181, + "[full_DEL]": 1183, + "[full_full_stop]": 1218, + "[full_left_corner_bracket]": 1220, + "[full_right_corner_bracket]": 1222, + "[full_comma]": 1224, + "[full_interpunct]": 1226, + "[full_kata_wo]": 1228, + "[full_kata_small_a]": 1230, + "[full_kata_small_i]": 1232, + "[full_kata_small_u]": 1234, + "[full_kata_small_e]": 1236, + "[full_kata_small_o]": 1238, + "[full_kata_small_ya]": 1240, + "[full_kata_small_yu]": 1242, + "[full_kata_small_yo]": 1244, + "[full_kata_small_tu]": 1246, + "[full_long_vowel]": 1280, + "[full_kata_a]": 1282, + "[full_kata_i]": 1284, + "[full_kata_u]": 1286, + "[full_kata_e]": 1288, + "[full_kata_o]": 1290, + "[full_kata_ka]": 1292, + "[full_kata_ki]": 1294, + "[full_kata_ku]": 1296, + "[full_kata_ke]": 1298, + "[full_kata_ko]": 1300, + "[full_kata_sa]": 1302, + "[full_kata_si]": 1304, + "[full_kata_su]": 1306, + "[full_kata_se]": 1308, + "[full_kata_so]": 1310, + "[full_kata_ta]": 1344, + "[full_kata_ti]": 1346, + "[full_kata_tu]": 1348, + "[full_kata_te]": 1350, + "[full_kata_to]": 1352, + "[full_kata_na]": 1354, + "[full_kata_ni]": 1356, + "[full_kata_nu]": 1358, + "[full_kata_ne]": 1360, + "[full_kata_no]": 1362, + "[full_kata_ha]": 1364, + "[full_kata_hi]": 1366, + "[full_kata_hu]": 1368, + "[full_kata_he]": 1370, + "[full_kata_ho]": 1372, + "[full_kata_ma]": 1374, + "[full_kata_mi]": 1408, + "[full_kata_mu]": 1410, + "[full_kata_me]": 1412, + "[full_kata_mo]": 1414, + "[full_kata_ya]": 1416, + "[full_kata_yu]": 1418, + "[full_kata_yo]": 1420, + "[full_kata_ra]": 1422, + "[full_kata_ri]": 1424, + "[full_kata_ru]": 1426, + "[full_kata_re]": 1428, + "[full_kata_ro]": 1430, + "[full_kata_wa]": 1432, + "[full_kata_n]": 1434, + "[full_dakuten]": 1436, + "[full_handakuten]": 1438, + "[full_kata_da]": 1472, + "[full_kata_di]": 1474, + "[full_kata_du]": 1476, + "[full_kata_de]": 1478, + "[full_kata_do]": 1480, + "[full_kata_ba]": 1492, + "[full_kata_bi]": 1494, + "[full_kata_bu]": 1496, + "[full_kata_be]": 1498, + "[full_kata_bo]": 1500, + "[full_kata_pa]": 1502, + "[full_kata_pi]": 1536, + "[full_kata_pu]": 1538, + "[full_kata_pe]": 1540, + "[full_kata_po]": 1542, + "[full_kata_ga]": 1548, + "[full_kata_gi]": 1550, + "[full_kata_gu]": 1552, + "[full_kata_ge]": 1554, + "[full_kata_go]": 1556, + "[full_kata_za]": 1558, + "[full_kata_zi]": 1560, + "[full_kata_zu]": 1562, + "[full_kata_ze]": 1564, + "[full_kata_zo]": 1566, + "[full_hira_wo]": 1612, + "[full_hira_small_a]": 1614, + "[full_hira_small_i]": 1616, + "[full_hira_small_u]": 1618, + "[full_hira_small_e]": 1620, + "[full_hira_small_o]": 1622, + "[full_hira_small_ya]": 1624, + "[full_hira_small_yu]": 1626, + "[full_hira_small_yo]": 1628, + "[full_hira_small_tu]": 1630, + "[full_hira_a]": 1666, + "[full_hira_i]": 1668, + "[full_hira_u]": 1670, + "[full_hira_e]": 1672, + "[full_hira_o]": 1674, + "[full_hira_ka]": 1676, + "[full_hira_ki]": 1678, + "[full_hira_ku]": 1680, + "[full_hira_ke]": 1682, + "[full_hira_ko]": 1684, + "[full_hira_sa]": 1686, + "[full_hira_si]": 1688, + "[full_hira_su]": 1690, + "[full_hira_se]": 1692, + "[full_hira_so]": 1694, + "[full_hira_ta]": 1728, + "[full_hira_ti]": 1730, + "[full_hira_tu]": 1732, + "[full_hira_te]": 1734, + "[full_hira_to]": 1736, + "[full_hira_na]": 1738, + "[full_hira_ni]": 1740, + "[full_hira_nu]": 1742, + "[full_hira_ne]": 1744, + "[full_hira_no]": 1746, + "[full_hira_ha]": 1748, + "[full_hira_hi]": 1750, + "[full_hira_hu]": 1752, + "[full_hira_he]": 1754, + "[full_hira_ho]": 1756, + "[full_hira_ma]": 1758, + "[full_hira_mi]": 1792, + "[full_hira_mu]": 1794, + "[full_hira_me]": 1796, + "[full_hira_mo]": 1798, + "[full_hira_ya]": 1800, + "[full_hira_yu]": 1802, + "[full_hira_yo]": 1804, + "[full_hira_ra]": 1806, + "[full_hira_ri]": 1808, + "[full_hira_ru]": 1810, + "[full_hira_re]": 1812, + "[full_hira_ro]": 1814, + "[full_hira_wa]": 1816, + "[full_hira_n]": 1818, + "[full_hira_da]": 1856, + "[full_hira_di]": 1858, + "[full_hira_du]": 1860, + "[full_hira_de]": 1862, + "[full_hira_do]": 1864, + "[full_hira_ba]": 1876, + "[full_hira_bi]": 1878, + "[full_hira_bu]": 1880, + "[full_hira_be]": 1882, + "[full_hira_bo]": 1884, + "[full_hira_pa]": 1886, + "[full_hira_pi]": 1920, + "[full_hira_pu]": 1922, + "[full_hira_pe]": 1924, + "[full_hira_po]": 1926, + "[full_hira_ga]": 1932, + "[full_hira_gi]": 1934, + "[full_hira_gu]": 1936, + "[full_hira_ge]": 1938, + "[full_hira_go]": 1940, + "[full_hira_za]": 1942, + "[full_hira_zi]": 1944, + "[full_hira_zu]": 1946, + "[full_hira_ze]": 1948, + "[full_hira_zo]": 1950, + "[full_X]": 1984, + "[full_tilde]": 1990, + "[full_left_white_corner_bracket]": 1992, + "[full_right_white_corner_bracket]": 1994, + "[full_celsius]": 1996 + } + }, + { + "regions": [ + "U", + "J", + "E" + ], + "chars": { + "絵": 2048, + "映": 2050, + "暗": 2052, + "億": 2054, + "遺": 2056, + "教": 2058, + "帰": 2060, + "家": 2062, + "願": 2064, + "語": 2066, + "期": 2068, + "経": 2070, + "波": 2072, + "怪": 2074, + "協": 2076, + "恐": 2078, + "宅": 2112, + "直": 2114, + "止": 2116, + "砂": 2118, + "代": 2120, + "対": 2122, + "点": 2124, + "単": 2126, + "罪": 2128, + "付": 2130, + "使": 2132, + "月": 2134, + "詰": 2136, + "平": 2138, + "伝": 2140, + "正": 2142, + "係": 2176, + "警": 2178, + "議": 2180, + "危": 2182, + "険": 2184, + "効": 2186, + "客": 2188, + "器": 2190, + "室": 2192, + "床": 2194, + "推": 2196, + "笑": 2198, + "週": 2200, + "説": 2202, + "成": 2204, + "参": 2206, + "通": 2240, + "定": 2242, + "長": 2244, + "当": 2246, + "他": 2248, + "大": 2250, + "供": 2252, + "出": 2254, + "天": 2256, + "血": 2258, + "立": 2260, + "力": 2262, + "度": 2264, + "友": 2266, + "津": 2268, + "田": 2270, + "茶": 2304, + "剣": 2306, + "修": 2308, + "卒": 2310, + "桁": 2312, + "察": 2314, + "絶": 2316, + "師": 2318, + "続": 2320, + "証": 2322, + "張": 2324, + "担": 2326, + "筒": 2328, + "談": 2330, + "途": 2332, + "能": 2334, + "段": 2368, + "電": 2370, + "知": 2372, + "体": 2374, + "中": 2376, + "手": 2378, + "地": 2380, + "打": 2382, + "台": 2384, + "冗": 2386, + "忠": 2388, + "虫": 2390, + "複": 2392, + "団": 2394, + "届": 2396, + "探": 2398, + "刃": 2432, + "風": 2434, + "怖": 2436, + "被": 2438, + "歩": 2440, + "杖": 2442, + "妙": 2444, + "夜": 2446, + "預": 2448, + "利": 2450, + "夫": 2452, + "軽": 2454, + "青": 2456, + "色": 2458, + "足": 2460, + "安": 2462, + "偵": 2496, + "呑": 2498, + "念": 2500, + "内": 2502, + "任": 2504, + "難": 2506, + "何": 2508, + "仲": 2510, + "泣": 2512, + "二": 2514, + "人": 2516, + "年": 2518, + "名": 2520, + "也": 2522, + "肌": 2524, + "原": 2526, + "息": 2560, + "重": 2562, + "追": 2564, + "海": 2566, + "朝": 2568, + "起": 2570, + "居": 2572, + "相": 2574, + "育": 2576, + "折": 2578, + "多": 2580, + "同": 2582, + "以": 2584, + "姉": 2586, + "妹": 2588, + "赤": 2590, + "東": 2624, + "弁": 2626, + "並": 2628, + "文": 2630, + "羽": 2632, + "払": 2634, + "火": 2636, + "非": 2638, + "古": 2640, + "閉": 2642, + "忘": 2644, + "花": 2646, + "別": 2648, + "母": 2650, + "変": 2652, + "光": 2654, + "送": 2688, + "院": 2690, + "英": 2692, + "雨": 2694, + "悪": 2696, + "音": 2698, + "員": 2700, + "一": 2702, + "空": 2704, + "入": 2706, + "会": 2708, + "男": 2710, + "丑": 2712, + "合": 2714, + "引": 2716, + "言": 2718, + "包": 2752, + "丁": 2754, + "左": 2756, + "辺": 2758, + "不": 2760, + "晩": 2762, + "場": 2764, + "本": 2766, + "服": 2768, + "必": 2770, + "返": 2772, + "品": 2774, + "部": 2776, + "亡": 2778, + "広": 2780, + "吹": 2782, + "意": 2816, + "今": 2818, + "即": 2820, + "石": 2822, + "営": 2824, + "忙": 2826, + "油": 2828, + "押": 2830, + "位": 2832, + "苦": 2834, + "川": 2836, + "更": 2838, + "介": 2840, + "幸": 2842, + "肩": 2844, + "首": 2846, + "分": 2880, + "日": 2882, + "番": 2884, + "封": 2886, + "犯": 2888, + "星": 2890, + "発": 2892, + "半": 2894, + "久": 2896, + "身": 2898, + "眉": 2900, + "面": 2902, + "命": 2904, + "末": 2906, + "明": 2908, + "丸": 2910, + "河": 2944, + "計": 2946, + "可": 2948, + "具": 2950, + "凶": 2952, + "解": 2954, + "号": 2956, + "頃": 2958, + "加": 2960, + "開": 2962, + "果": 2964, + "外": 2966, + "片": 2968, + "向": 2970, + "北": 2972, + "悲": 2974, + "毎": 3008, + "店": 3010, + "町": 3012, + "問": 3014, + "元": 3016, + "昔": 3018, + "的": 3020, + "戻": 3022, + "見": 3024, + "美": 3026, + "間": 3028, + "守": 3030, + "味": 3032, + "前": 3034, + "目": 3036, + "万": 3038, + "刑": 3072, + "決": 3074, + "官": 3076, + "交": 3078, + "来": 3080, + "込": 3082, + "午": 3084, + "香": 3086, + "景": 3088, + "害": 3090, + "角": 3092, + "高": 3094, + "回": 3096, + "固": 3098, + "画": 3100, + "急": 3102, + "求": 3136, + "了": 3138, + "恵": 3140, + "余": 3142, + "指": 3144, + "役": 3146, + "世": 3148, + "夕": 3150, + "屋": 3152, + "由": 3154, + "要": 3156, + "奴": 3158, + "予": 3160, + "故": 3162, + "許": 3164, + "翌": 3166, + "覚": 3200, + "関": 3202, + "心": 3204, + "気": 3206, + "休": 3208, + "彼": 3210, + "旧": 3212, + "木": 3214, + "薬": 3216, + "件": 3218, + "声": 3220, + "金": 3222, + "行": 3224, + "近": 3226, + "口": 3228, + "子": 3230, + "洋": 3264, + "用": 3266, + "有": 3268, + "山": 3270, + "労": 3272, + "老": 3274, + "礼": 3276, + "理": 3278, + "落": 3280, + "頼": 3282, + "良": 3284, + "留": 3286, + "冷": 3288, + "乱": 3290, + "訳": 3292, + "若": 3294, + "黒": 3328, + "車": 3330, + "方": 3332, + "現": 3334, + "学": 3336, + "校": 3338, + "買": 3340, + "柱": 3342, + "工": 3344, + "困": 3346, + "完": 3348, + "仮": 3350, + "形": 3352, + "五": 3354, + "記": 3356, + "岸": 3358, + "業": 3392, + "話": 3394, + "々": 3396, + "右": 3398, + "衛": 3400, + "宇": 3402, + "考": 3404, + "強": 3406, + "楽": 3408, + "後": 3410, + "顔": 3412, + "界": 3414, + "吸": 3416, + "軌": 3418, + "科": 3420, + "携": 3422, + "告": 3456, + "甘": 3458, + "曲": 3460, + "缶": 3462, + "ヶ": 3464, + "食": 3466, + "消": 3468, + "三": 3470, + "背": 3472, + "差": 3474, + "謝": 3476, + "次": 3478, + "詳": 3480, + "住": 3482, + "小": 3484, + "申": 3486, + "獲": 3520, + "確": 3522, + "銀": 3524, + "研": 3526, + "究": 3528, + "壊": 3530, + "戦": 3532, + "勝": 3534, + "組": 3536, + "受": 3538, + "初": 3540, + "側": 3542, + "丈": 3544, + "頭": 3546, + "巣": 3548, + "算": 3550, + "叫": 3584, + "支": 3586, + "村": 3588, + "下": 3590, + "争": 3592, + "借": 3594, + "全": 3596, + "財": 3598, + "社": 3600, + "線": 3602, + "写": 3604, + "仕": 3606, + "真": 3608, + "走": 3610, + "去": 3612, + "早": 3614, + "助": 3648, + "取": 3650, + "持": 3652, + "乗": 3654, + "終": 3656, + "数": 3658, + "装": 3660, + "常": 3662, + "進": 3664, + "順": 3666, + "最": 3668, + "然": 3670, + "親": 3672, + "私": 3674, + "残": 3676, + "存": 3678, + "昨": 3712, + "路": 3714, + "好": 3716, + "性": 3718, + "失": 3720, + "情": 3722, + "者": 3724, + "時": 3726, + "作": 3728, + "士": 3730, + "材": 3732, + "字": 3734, + "弱": 3736, + "上": 3738, + "肖": 3740, + "査": 3742, + "挑": 3776, + "敵": 3778, + "第": 3780, + "転": 3782, + "投": 3784, + "弾": 3786, + "著": 3788, + "置": 3790, + "脱": 3792, + "態": 3794, + "調": 3796, + "特": 3798, + "宙": 3800, + "負": 3802, + "比": 3804, + "宝": 3806, + "実": 3840, + "少": 3842, + "女": 3844, + "与": 3846, + "所": 3848, + "思": 3850, + "姿": 3852, + "自": 3854, + "事": 3856, + "殺": 3858, + "信": 3860, + "先": 3862, + "生": 3864, + "才": 3866, + "死": 3868, + "眠": 3870, + "物": 3904, + "聞": 3906, + "敗": 3908, + "配": 3910, + "爆": 3912, + "邦": 3914, + "捕": 3916, + "無": 3918, + "滅": 3920, + "勇": 3922, + "唯": 3924, + "優": 3926, + "両": 3928, + "連": 3930, + "惑": 3932, + "和": 3934, + "主": 3968, + "署": 3970, + "水": 3972, + "白": 3974, + "束": 3976, + "志": 3978, + "在": 3980, + "叩": 3982, + "託": 3984, + "突": 3986, + "道": 3988, + "土": 3990, + "机": 3992, + "戸": 3994, + "沈": 3996, + "注": 3998, + "務": 4032, + "草": 4034, + "流": 4036, + "帳": 4038, + "徒": 4040, + "呼": 4042, + "毛": 4044, + "補": 4046, + "結": 4048, + "壁": 4050, + "応": 4052, + "待": 4054, + "筆": 4056, + "産": 4058, + "報": 4060, + "武": 4062, + "政": 4096, + "範": 4098, + "収": 4100, + "凍": 4102, + "囲": 4104, + "拡": 4106, + "環": 4108, + "貫": 4110, + "繰": 4112, + "越": 4114, + "給": 4116, + "散": 4118, + "案": 4120, + "速": 4122, + "低": 4124, + "感": 4126, + "厚": 4160, + "民": 4162, + "岩": 4164, + "汚": 4166, + "展": 4168, + "化": 4170, + "動": 4172, + "資": 4174, + "総": 4176, + "額": 4178, + "巨": 4180, + "耳": 4182, + "功": 4184, + "得": 4186, + "較": 4188, + "級": 4190, + "様": 4224, + "済": 4226, + "因": 4228, + "況": 4230, + "暴": 4232, + "再": 4234, + "災": 4236, + "提": 4238, + "反": 4240, + "治": 4242, + "活": 4244, + "制": 4246, + "限": 4248, + "都": 4250, + "達": 4252, + "冒": 4254, + "量": 4288, + "輸": 4290, + "導": 4292, + "策": 4294, + "増": 4296, + "溶": 4298, + "頂": 4300, + "南": 4302, + "省": 4304, + "氷": 4306, + "浪": 4308, + "費": 4310, + "矢": 4312, + "玄": 4314, + "貼": 4316, + "境": 4318, + "減": 4352, + "破": 4354, + "鎮": 4356, + "震": 4358, + "奪": 4360, + "処": 4362, + "建": 4364, + "興": 4366, + "温": 4368, + "摸": 4370, + "満": 4372, + "極": 4374, + "球": 4376, + "暖": 4378, + "紀": 4380, + "昇": 4382, + "惜": 4416, + "復": 4418, + "洪": 4420, + "放": 4422, + "射": 4424, + "染": 4426, + "周": 4428, + "区": 4430, + "商": 4432, + "未": 4434, + "鉄": 4436, + "港": 4438, + "板": 4440, + "福": 4442, + "漁": 4444, + "崖": 4446, + "飛": 4480, + "機": 4482, + "竜": 4484, + "巻": 4486, + "新": 4488, + "誕": 4490, + "船": 4492, + "碑": 4494, + "競": 4496, + "技": 4498, + "病": 4500, + "公": 4502, + "園": 4504, + "森": 4506, + "林": 4508, + "帯": 4510, + "域": 4544, + "防": 4546, + "割": 4548, + "率": 4550, + "税": 4552, + "渋": 4554, + "滞": 4556, + "殲": 4558, + "評": 4560, + "価": 4562, + "規": 4564, + "易": 4566, + "題": 4568, + "絡": 4570, + "格": 4572, + "辞": 4574, + "儀": 4608, + "荷": 4610, + "切": 4612, + "便": 4614, + "運": 4616, + "威": 4618, + "着": 4620, + "暦": 4622, + "異": 4624, + "局": 4626, + "君": 4628, + "軍": 4630, + "誰": 4632, + "隊": 4634, + "示": 4636, + "令": 4638, + "論": 4672, + "擬": 4674, + "移": 4676, + "違": 4678, + "我": 4680, + "掛": 4682, + "敢": 4684, + "還": 4686, + "寄": 4688, + "激": 4690, + "遣": 4692, + "厳": 4694, + "誤": 4696, + "拘": 4698, + "獄": 4700, + "根": 4702, + "識": 4736, + "邪": 4738, + "承": 4740, + "衝": 4742, + "殖": 4744, + "図": 4746, + "凄": 4748, + "選": 4750, + "葬": 4752, + "退": 4754, + "択": 4756, + "断": 4758, + "撤": 4760, + "到": 4762, + "肉": 4764, + "派": 4766, + "判": 4800, + "秘": 4802, + "表": 4804, + "臆": 4806, + "迷": 4808, + "黙": 4810, + "容": 4812, + "欲": 4814, + "握": 4816, + "影": 4818, + "改": 4820, + "管": 4822, + "肝": 4824, + "及": 4826, + "救": 4828, + "響": 4830, + "系": 4864, + "撃": 4866, + "源": 4868, + "庫": 4870, + "攻": 4872, + "細": 4874, + "際": 4876, + "飼": 4878, + "質": 4880, + "集": 4882, + "除": 4884, + "障": 4886, + "状": 4888, + "侵": 4890, + "潜": 4892, + "善": 4894, + "阻": 4928, + "促": 4930, + "捉": 4932, + "測": 4934, + "替": 4936, + "程": 4938, + "努": 4940, + "倒": 4942, + "答": 4944, + "統": 4946, + "逃": 4948, + "闘": 4950, + "認": 4952, + "把": 4954, + "抜": 4956, + "備": 4958, + "保": 4992, + "銘": 4994, + "逆": 4996, + "敬": 4998, + "験": 5000, + "司": 5002, + "秀": 5004, + "神": 5006, + "想": 5008, + "徹": 5010, + "皮": 5012, + "乏": 5014, + "従": 5016, + "条": 5018, + "迫": 5020, + "却": 5022, + "圧": 5056, + "扱": 5058, + "為": 5060, + "延": 5062, + "援": 5064, + "央": 5066, + "憶": 5068, + "恩": 5070, + "過": 5072, + "充": 5074, + "寒": 5076, + "簡": 5078, + "基": 5080, + "奇": 5082, + "揮": 5084, + "貴": 5086, + "疑": 5120, + "虐": 5122, + "驚": 5124, + "禁": 5126, + "緊": 5128, + "駆": 5130, + "偶": 5132, + "型": 5134, + "継": 5136, + "銃": 5138, + "御": 5140, + "悟": 5142, + "護": 5144, + "抗": 5146, + "刻": 5148, + "鎖": 5150, + "載": 5184, + "始": 5186, + "施": 5188, + "至": 5190, + "視": 5192, + "歯": 5194, + "習": 5196, + "臭": 5198, + "襲": 5200, + "縦": 5202, + "宿": 5204, + "招": 5206, + "祥": 5208, + "称": 5210, + "象": 5212, + "剰": 5214, + "植": 5248, + "織": 5250, + "職": 5252, + "触": 5254, + "振": 5256, + "浸": 5258, + "深": 5260, + "辛": 5262, + "尽": 5264, + "遂": 5266, + "勢": 5268, + "棲": 5270, + "醒": 5272, + "析": 5274, + "責": 5276, + "接": 5278, + "設": 5312, + "狙": 5314, + "掃": 5316, + "操": 5318, + "像": 5320, + "損": 5322, + "駄": 5324, + "怠": 5326, + "貸": 5328, + "填": 5330, + "築": 5332, + "徴": 5334, + "超": 5336, + "鳥": 5338, + "珍": 5340, + "痛": 5342, + "適": 5376, + "搭": 5378, + "踏": 5380, + "伸": 5382, + "毒": 5384, + "熱": 5386, + "脳": 5388, + "否": 5390, + "避": 5392, + "短": 5394, + "富": 5396, + "浮": 5398, + "副": 5400, + "抱": 5402, + "法": 5404, + "胞": 5406, + "豊": 5440, + "望": 5442, + "謀": 5444, + "魅": 5446, + "密": 5448, + "耗": 5450, + "厄": 5452, + "約": 5454, + "躍": 5456, + "裕": 5458, + "幼": 5460, + "葉": 5462, + "裏": 5464, + "離": 5466, + "隣": 5468, + "裂": 5470, + "炉": 5504, + "戮": 5506, + "値": 5508, + "犠": 5510, + "降": 5512, + "牲": 5514, + "妨": 5516, + "貪": 5518, + "試": 5520, + "殊": 5522, + "餌": 5524, + "液": 5526, + "械": 5528, + "耐": 5530, + "例": 5532, + "愛": 5534, + "兵": 5568, + "依": 5570, + "奥": 5572, + "蝕": 5574, + "枢": 5576, + "跡": 5578, + "素": 5580, + "締": 5582, + "艇": 5584, + "秒": 5586, + "陸": 5588, + "距": 5590, + "普": 5592, + "漢": 5594, + "種": 5596, + "類": 5598 + } + }, + { + "regions": [ + "E", + "C" + ], + "chars": { + "[hundred_percent]": 793 + } + }, + { + "regions": [ + "C" + ], + "chars": { + "…": 1484, + "!": 1486, + "可": 2048, + "远": 2050, + "距": 2052, + "离": 2054, + "射": 2056, + "击": 2058, + "按": 2060, + "斜": 2062, + "上": 2064, + "方": 2066, + "扫": 2068, + "住": 2070, + "蓄": 2072, + "力": 2074, + "释": 2076, + "放": 2078, + "键": 2112, + "则": 2114, + "发": 2116, + "攻": 2118, + "全": 2120, + "跳": 2122, + "起": 2124, + "瞬": 2126, + "时": 2128, + "冷": 2130, + "冻": 2132, + "敌": 2134, + "人": 2136, + "安": 2138, + "站": 2140, + "在": 2142, + "已": 2176, + "被": 2178, + "的": 2180, + "该": 2182, + "光": 2184, + "束": 2186, + "由": 2188, + "能": 2190, + "量": 2192, + "波": 2194, + "聚": 2196, + "集": 2198, + "而": 2200, + "成": 2202, + "透": 2204, + "坚": 2206, + "硬": 2240, + "物": 2242, + "体": 2244, + "强": 2246, + "大": 2248, + "依": 2250, + "次": 2252, + "穿": 2254, + "多": 2256, + "个": 2258, + "开": 2260, + "火": 2262, + "打": 2264, + "红": 2266, + "门": 2268, + "装": 2270, + "备": 2304, + "有": 2306, + "绿": 2308, + "变": 2310, + "形": 2312, + "为": 2314, + "球": 2316, + "状": 2318, + "态": 2320, + "黄": 2322, + "减": 2324, + "少": 2326, + "来": 2328, + "自": 2330, + "伤": 2332, + "害": 2334, + "且": 2368, + "抗": 2370, + "酸": 2372, + "热": 2374, + "降": 2376, + "低": 2378, + "以": 2380, + "水": 2382, + "中": 2384, + "梭": 2386, + "还": 2388, + "避": 2390, + "免": 2392, + "熔": 2394, + "岩": 2396, + "侵": 2398, + "双": 2432, + "就": 2434, + "从": 2436, + "狭": 2438, + "小": 2440, + "通": 2442, + "道": 2444, + "过": 2446, + "抓": 2448, + "并": 2450, + "悬": 2452, + "挂": 2454, + "边": 2456, + "缘": 2458, + "和": 2460, + "拐": 2462, + "弯": 2496, + "处": 2498, + "引": 2500, + "擎": 2502, + "停": 2504, + "止": 2506, + "加": 2508, + "速": 2510, + "后": 2512, + "高": 2514, + "撞": 2516, + "破": 2518, + "某": 2520, + "些": 2522, + "障": 2524, + "碍": 2526, + "重": 2560, + "创": 2562, + "增": 2564, + "跃": 2566, + "最": 2568, + "度": 2570, + "弹": 2572, + "翻": 2574, + "筋": 2576, + "斗": 2578, + "消": 2580, + "灭": 2582, + "空": 2584, + "连": 2586, + "续": 2588, + "转": 2590, + "再": 2624, + "分": 2626, + "析": 2628, + "结": 2630, + "果": 2632, + "是": 2634, + "不": 2636, + "确": 2638, + "定": 2640, + "当": 2642, + "先": 2644, + "防": 2646, + "护": 2648, + "服": 2650, + "兼": 2652, + "容": 2654, + "质": 2688, + "一": 2690, + "件": 2692, + "动": 2694, + "手": 2696, + "枪": 2698, + "得": 2700, + "晕": 2702, + "头": 2704, + "向": 2706, + "子": 2708, + "假": 2710, + "报": 2712, + "文": 2714, + "获": 2716, + "槽": 2718, + "导": 2752, + "超": 2754, + "级": 2756, + "磅": 2758, + "炸": 2760, + "长": 2762, + "冰": 2764, + "未": 2766, + "知": 2768, + "品": 2770, + "器": 2772, + "保": 2774, + "存": 2776, + "否": 2778, + "完": 2780, + "毕": 2782, + "武": 2816, + "补": 2818, + "给": 2820, + "恢": 2822, + "复": 2824, + "布": 2826, + "林": 2828, + "斯": 2830, + "塔": 2832, + "地": 2834, + "图": 2836, + "数": 2838, + "据": 2840, + "收": 2842, + "克": 2844, + "雷": 2846, + "德": 2880, + "诺": 2882, + "尔": 2884, + "飞": 2886, + "亚": 2888, + "利": 2890, + "母": 2892, + "舰": 2894, + "你": 2896, + "经": 2898, + "到": 2900, + "传": 2902, + "说": 2904, + "我": 2906, + "毁": 2908, + "程": 2910, + "序": 2944, + "启": 2946, + "立": 2948, + "即": 2950, + "疏": 2952, + "散": 2954, + "位": 2956, + "异": 2958, + "常": 2960, + "检": 2962, + "查": 2964, + "信": 2966, + "息": 2968, + "置": 2970, + "短": 2972, + "暂": 2974, + "休": 3008, + "眼": 3010, + "退": 3012, + "出": 3014, + "同": 3016, + "下": 3018, + "泽": 3020, + "贝": 3022, + "行": 3024, + "星": 3026, + "邪": 3028, + "恶": 3030, + "尚": 3032, + "临": 3034, + "这": 3036, + "片": 3038, + "曾": 3072, + "祥": 3074, + "土": 3076, + "把": 3078, + "里": 3080, + "称": 3082, + "做": 3084, + "家": 3086, + "乡": 3088, + "现": 3090, + "终": 3092, + "于": 3094, + "要": 3096, + "追": 3098, + "述": 3100, + "第": 3102, + "场": 3136, + "战": 3138, + "了": 3140, + "它": 3142, + "叫": 3144, + "零": 3146, + "点": 3148, + "任": 3150, + "务": 3152, + "萨": 3154, + "姆": 3156, + "艾": 3158, + "仁": 3160, + "关": 3162, + "逃": 3164, + "脱": 3166, + "真": 3200, + "糟": 3202, + "糕": 3204, + "没": 3206, + "情": 3208, + "况": 3210, + "宇": 3212, + "宙": 3214, + "海": 3216, + "盜": 3218, + "袭": 3220, + "狼": 3222, + "狈": 3224, + "用": 3226, + "身": 3228, + "只": 3230, + "实": 3264, + "际": 3266, + "几": 3268, + "乎": 3270, + "救": 3272, + "急": 3274, + "想": 3276, + "样": 3278, + "深": 3280, + "入": 3282, + "简": 3284, + "直": 3286, + "愚": 3288, + "蠢": 3290, + "之": 3292, + "极": 3294, + "别": 3328, + "无": 3330, + "选": 3332, + "择": 3334, + "活": 3336, + "着": 3338, + "吗": 3340, + "紧": 3342, + "命": 3344, + "令": 3346, + "所": 3348, + "密": 3350, + "特": 3352, + "罗": 3354, + "生": 3356, + "败": 3358, + "机": 3392, + "械": 3394, + "脑": 3396, + "游": 3398, + "戏": 3400, + "始": 3402, + "载": 3404, + "拷": 3406, + "将": 3408, + "进": 3410, + "覆": 3412, + "盖": 3414, + "原": 3416, + "清": 3418, + "除": 3420, + "坏": 3422, + "新": 3456, + "档": 3458, + "储": 3460, + "继": 3462, + "快": 3464, + "汉": 3466, + "字": 3468, + "平": 3470, + "名": 3472, + "难": 3474, + "设": 3476, + "单": 3478, + "般": 3480, + "易": 3482, + "困": 3484, + "删": 3486, + "会": 3520, + "链": 3522, + "接": 3524, + "请": 3526, + "稍": 3528, + "候": 3530, + "融": 3532, + "合": 3534, + "包": 3536, + "错": 3538, + "误": 3540, + "闭": 3542, + "系": 3544, + "统": 3546, + "然": 3548, + "认": 3550, + "线": 3584, + "正": 3586, + "电": 3588, + "源": 3590, + "切": 3592, + "断": 3594, + "佳": 3596, + "间": 3598, + "份": 3600, + "验": 3602, + "证": 3604, + "码": 3606, + "试": 3608, + "画": 3610, + "廊": 3612, + "添": 3614, + "项": 3648, + "栏": 3650, + "纪": 3652, + "录": 3654, + "声": 3656, + "音": 3658, + "测": 3660, + "泰": 3662, + "昂": 3664, + "格": 3666, + "乔": 3668, + "佐": 3670, + "迪": 3672, + "记": 3674, + "研": 3676, + "究": 3678, + "室": 3712, + "鸟": 3714, + "遗": 3716, + "迹": 3718, + "艇": 3720, + "换": 3722, + "移": 3724, + "对": 3728, + "造": 3730, + "失": 3732, + "满": 3734, + "昏": 3736, + "团": 3738, + "旋": 3740, + "前": 3776, + "乐": 3780, + "欣": 3782, + "赏": 3784, + "联": 3786, + "制": 3788, + "等": 3794, + "普": 3798, + "法": 3802, + "助": 3842, + "跑": 3844, + "执": 3846, + "哪": 3850 + } + } +] \ No newline at end of file From 81ff0a3fd5a5d253fd5c2ebcab09483af1f2d238 Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:58:13 -0800 Subject: [PATCH 5/7] Increase patcher free space --- src/mars_patcher/zm/constants/reserved_space.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mars_patcher/zm/constants/reserved_space.py b/src/mars_patcher/zm/constants/reserved_space.py index b783821..7a83b8a 100644 --- a/src/mars_patcher/zm/constants/reserved_space.py +++ b/src/mars_patcher/zm/constants/reserved_space.py @@ -12,7 +12,7 @@ class ReservedConstantsZM: # Important addresses: # 0x760D38 - End of vanilla data (U region) - # 0x7B0000 - Patcher data free space + # 0x790000 - Patcher data free space # 0x7D0000 - Randomizer data pointers # 0x7D8000 - NES Metroid data @@ -20,7 +20,7 @@ class ReservedConstantsZM: RANDO_POINTERS_ADDR = 0x7D0000 # Address for any additional data that the patcher may need to write - PATCHER_FREE_SPACE_ADDR = 0x7B0000 + PATCHER_FREE_SPACE_ADDR = 0x790000 PATCHER_FREE_SPACE_END = RANDO_POINTERS_ADDR From eab39a46218f518a2cf08f63efe01df6e733cb06 Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:46:28 -0700 Subject: [PATCH 6/7] Fix tile_val scope --- src/mars_patcher/zm/item_patcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mars_patcher/zm/item_patcher.py b/src/mars_patcher/zm/item_patcher.py index f00f845..d8c803c 100644 --- a/src/mars_patcher/zm/item_patcher.py +++ b/src/mars_patcher/zm/item_patcher.py @@ -82,7 +82,6 @@ def add_item_graphics(self, sprite: ItemSprite) -> int: raise ValueError("No blank entry found in animated tileset") # Find blank tiles in tilemap - tile_val = -1 block_num = -1 for i in range(0x4C, 0x50): offset = i * 4 @@ -92,7 +91,7 @@ def add_item_graphics(self, sprite: ItemSprite) -> int: self.tilemap.data[offset + t] = tile_val + t block_num = i break - if tile_val == -1 or block_num == -1: + if block_num == -1: raise ValueError("No blank tiles found in tilemap") return block_num From 51a2cae9aecb32bc01b345f1800d99bb0df6d803 Mon Sep 17 00:00:00 2001 From: biosp4rk <37962487+biosp4rk@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:06:39 -0700 Subject: [PATCH 7/7] Remove unnecessary print --- src/mars_patcher/text.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mars_patcher/text.py b/src/mars_patcher/text.py index e84a6c3..130125c 100644 --- a/src/mars_patcher/text.py +++ b/src/mars_patcher/text.py @@ -202,7 +202,6 @@ def encode_text( if message_type == MessageType.ONE_LINE: raise ValueError(f'String does not fit on one line:\n"{string}"') if width_since_break > max_width: - print(f"{char_widths_addr:X}") raise ValueError(f'Word does not fit on one line:\n"{string}"') line_width = width_since_break line_number += 1