Skip to content

Changelog

All notable changes to MioROM will be documented in this file.

[1.1.0] — 2026-09-18

Added

  • Nintendo Game Boy Advance (GBA) ROM Engine (miorom.platforms.gba.rom / miorom.platforms.gba / miorom.rom.handlers.gba):
  • Implemented analytical parser, builder, header inspector, complement checksum recalculator, cartridge memory capacity manager, backup save detector/patcher, and deep multimedia/sound engine scanner for Game Boy Advance ROM images (GBARom, GBARomHandler, resolve_gba_region, create_synthetic_gba_rom, fix_gba_checksum, GBAHeaderStruct) with 100% zero external dependencies.
  • Implemented declarative 192-byte header binary structure (GBAHeaderStruct) strictly via miorom.core.schema.BinaryStruct ensuring unified binary schema compliance without raw struct calls.
  • Added header complement checksum calculation, validation, and in-place fixing (calculate_header_checksum(), is_header_checksum_valid(), fix_header_checksum()) implementing the hardware-exact complement formula chk = (SUM_{0xA0..0xBC} - 0x19) & 0xFF.
  • Added entry point decoding (entry_point), resolving the 24-bit relative ARM branch opcode at offset 0x00 to physical ARM cartridge space (0x08000000..0x09FFFFFF).
  • Added Nintendo GBA product code regional territory resolver (resolve_gba_region) identifying Japan (J), USA / English (E), Europe (P), Germany (D), France (F), Italy (I), Spain (S), Australia (U), and Worldwide releases from the 4th character of the Game Code.
  • Added cartridge capacity expansion and trimming (expand(), trim()), supporting power-of-two cartridge sizing from 1 MB up to 64 MB with hardware-accurate 0xFF padding (representing erased NOR/NAND flash memory).
  • Added cartridge backup media detection and automated SRAM patching (detect_save_type(), patch_save_to_sram()), identifying EEPROM, SRAM, FLASH512, and FLASH1M signatures and converting backup calls to standard 32KB SRAM for flashcarts and emulators.
  • Added Real-Time Clock (RTC) library signature detection (has_rtc, SIIRTC_V001) integrated directly into detect_save_type() and GBARomHandler metadata.
  • Added Nintendo logo in-place restoration (fix_logo()), restoring the mandatory 156-byte logo bitmap (0x04..0x9F) required by the GBA Boot ROM.
  • Added 32-bit GBA memory pointer operations and surgical relinking (ptr_to_offset(), offset_to_ptr(), find_pointers(), relink_pointers()), enabling seamless translation relocation across Game Pak waitstate spaces (0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D).
  • Added direct GBA BIOS software interrupt (SWI) scanner bridge (scan_swi_calls()), resolving embedded calls to BIOS routines (LZ77UnCompWram, CpuSet, RegisterRamReset, etc.).
  • Added deep asset and sound discovery scanners (find_lz10_streams() for Nintendo LZ77 compressed graphics and text streams; find_sappy_songs() integrating with SappyScanner for Music Player 2000 song table candidate discovery).
  • Added synthetic GBA ROM fixture generator (create_synthetic_gba_rom()) supporting custom title, game code, maker code, save types, RTC, LZ10 streams, and Sappy tables for 100% isolated unit testing without copyrighted binaries.
  • Registered GBARomHandler in RomManager with high precedence ahead of generic cartridge fallbacks.
  • Sony PlayStation 1 (PS1 / PS-X) ROM & Disc Engine (miorom.platforms.psx.rom / miorom.platforms.psx / miorom.rom.handlers.psx):
  • Implemented analytical parser, builder, virtual filesystem, SYSTEM.CNF configuration manager, executable manager, multi-media asset scanner, and dual sector serializer for Sony PlayStation 1 disc images (PSXRom, PSXFormat, PSXRomHandler, resolve_psx_region, create_synthetic_psx_iso, create_synthetic_psx_bin, CdSectorHeaderStruct, CdSubHeaderStruct) with 100% zero external dependencies.
  • Implemented declarative low-level CD sector structures (CdSectorHeaderStruct, CdSubHeaderStruct) strictly via miorom.core.schema.BinaryStruct ensuring unified binary schema compliance without raw struct calls.
  • Added dual sector format support: 2048-byte Mode 1 ISO disc images (PSXFormat.ISO) and 2352-byte Mode 2 Form 1 raw CD-ROM sector BIN images (PSXFormat.BIN) with automated sector size detection via ISO PVD magic \x01CD001 at sector 16.
  • Implemented Mode 2 Form 1 CD-ROM sector packaging with sync patterns (SYNC_PATTERN), BCD MSF address calculation (lba_to_msf), subheaders (\x00\x00\x08\x00), and bit-exact 32-bit EDC checksum recalculation (calculate_cdrom_edc) over 2056 bytes per sector.
  • Added SYSTEM.CNF parser and editor (system_cnf, boot_path, game_id, region), resolving boot executables, runtime task control blocks (TCB), event limits (EVENT), and stack pointers (STACK).
  • Added Sony PlayStation 1 product code region resolver (resolve_psx_region) identifying USA (SLUS, SCUS), EUR (SLES, SCES), JPN (SLPS, SLPM, SCPS), and ASIA (SLAJ) regional distribution territories.
  • Implemented virtual filesystem operations (list_files(), has_file(), get_file(), replace_file(), extract_all()) with automated ISO 9660 sector reallocation and extent relocation when injecting enlarged translation assets.
  • Added primary boot executable management (get_main_exe(), replace_main_exe()) interfacing with PSXExe for code/text editing, PC/GP adjustments, and MIPS text modifications.
  • Added deep multi-media discovery scanners (find_textures() for TIM textures, find_audio() for VAG audio streams, find_videos() for STR cutscene movie streams).
  • Added synthetic PS1 ISO and BIN fixture generators (create_synthetic_psx_iso(), create_synthetic_psx_bin()) for 100% isolated unit testing without copyrighted disc images.
  • Registered PSXRomHandler in RomManager with high precedence over generic ISO 9660 handlers.
  • Sony PSP AT3 (ATRAC3 & ATRAC3plus) Audio & Loop Engine (miorom.platforms.psp.at3 / miorom.platforms.psp):
  • Implemented analytical parser, builder, loop point editor, and stream replacer for Sony PlayStation Portable ATRAC3 (0x0270) and ATRAC3plus (0xFFFE extensible GUID) RIFF WAVE audio streams (AT3Audio, AT3Codec, AT3LoopPoint, create_synthetic_at3, RiffHeaderStruct, ChunkHeaderStruct, WaveFormatExStruct, Atrac3FormatExtraStruct, FactChunkStruct, SmplChunkHeaderStruct, SmplLoopEntryStruct) with 100% zero external dependencies.
  • Implemented declarative low-level binary headers strictly via miorom.core.schema.BinaryStruct ensuring unified struct processing without raw struct operations.
  • Added automatic audio codec detection distinguishing ATRAC3 (1024 samples/frame) and ATRAC3plus (2048 samples/frame) with Sony extensible SubFormat GUID verification.
  • Added comprehensive audio metrics inspection (channels, sample_rate, bitrate_kbps, frame_size, total_samples, duration_seconds).
  • Added BGM seamless loop point analysis and modification (is_looped, loop_point, set_loop(), remove_loop()), allowing translators and audio modders to set, adjust, or strip 'smpl' chunks so custom BGM does not stutter, desync, or silence.
  • Added audio payload replacement (replace_data()) with automated sample count recalculation across 'data', 'fact', and 'RIFF' chunk boundaries.
  • Added synthetic AT3 audio fixture generator (create_synthetic_at3()) supporting custom sample rates, channels, frame counts, and loop points for 100% isolated unit tests.
  • Integrated directly into PSPRom (get_boot_audio_at3(), set_boot_audio_at3(), set_boot_audio(), and deep stream scanner find_audio_streams()).
  • Sony PSP PRX Executable & NID Resolver Engine (miorom.platforms.psp.prx / miorom.platforms.psp):
  • Implemented analytical parser, builder, MIPS ELF inspector, bidirectional NID resolver, and MIPS call stub hooker for Sony PlayStation Portable Relocatable Executables (PRXModule, PSPNIDResolver, PRXModuleInfo, PRXImportLibrary, PRXExportLibrary, Elf32HeaderStruct, Elf32SectionHeaderStruct, SceModuleInfoStruct, SceLibraryEntryStruct, SceLibraryStubStruct, create_synthetic_prx) with 100% zero external dependencies.
  • Implemented declarative low-level binary structures strictly via miorom.core.schema.BinaryStruct ensuring unified binary parsing and serialization.
  • Added early encrypted executable detection (b"~PSP" signature) raising informative error guidance directing users to unencrypted PSP_GAME/SYSDIR/BOOT.BIN.
  • Added Sony sceModuleInfo 52-byte descriptor parsing, extracting module name, versions, $gp register initial value, user vs kernel privilege modes, and export/import table boundaries.
  • Implemented dynamic algorithmic SHA-1 32-bit NID hash calculation (calculate_nid()) and offline reverse lookup database mapping ~150+ official Sony SDK function hashes back to human-readable API names across sceDisplay, sceCtrl, sceIo, sceKernel, sceAudio, sceFont, scePower, sceUtility, sceRtc, sceGe, and sceUmd.
  • Implemented MIPS function call stub redirection (redirect_stub()) generating bit-exact MIPS j <target>; nop opcodes for hooking font rendering routines (VWF), filesystem redirection, and game script engines.
  • Added import NID swapping (replace_import_nid()) to redirect dynamic system calls directly within the PRX NID table.
  • Implemented virtual memory address resolution (va_to_offset()) translating section-mapped memory addresses and relocatable base-0 offsets into physical PRX file locations.
  • Added synthetic PRX fixture generator (create_synthetic_prx()) building valid 32-bit Little-Endian MIPS ELF PRX binaries for 100% isolated unit testing without copyrighted binaries.
  • Integrated directly into PSPRom (get_boot_prx(), replace_boot_prx(), get_eboot_prx(), replace_eboot_prx()).
  • Sony PSP Unified Disc & Package ROM Engine (miorom.platforms.psp.rom / miorom.platforms.psp / miorom.rom.handlers.psp):
  • Implemented analytical parser, builder, disc/package virtual filesystem, GUI asset editor, executable manager, and multi-format serializer for Sony PlayStation Portable ROMs (PSPRom, PSPFormat, PSPRomHandler, create_synthetic_psp_iso, create_synthetic_psp_pbp) with 100% zero external dependencies.
  • Added support for all major PSP formats: uncompressed UMD ISO 9660 disc images (PSPFormat.ISO), compressed CSO images (PSPFormat.CSO), and PlayStation EBOOT packages (PSPFormat.PBP).
  • Implemented seamless in-memory and on-disk format cross-conversion (to_bytes(fmt) and save(path, fmt)), enabling lossless ISO <-> CSO <-> PBP transitions.
  • Added comprehensive metadata inspection through embedded PARAM.SFO (sfo, game_id, title, version, category), paired with automatic Sony product code region detection (resolve_psp_region) identifying USA, EUR, JPN, ASIA, KOR, and Global releases.
  • Added XMB/VSH multimedia asset extraction and replacement (icon_bytes, background_bytes, boot_audio_bytes, set_title(), set_icon(), set_background()), enabling ROM translators to customize ICON0.PNG, PIC1.PNG, and SND0.AT3 without manual ISO rebuilding.
  • Implemented high-level virtual filesystem operations (list_files(), has_file(), get_file(), replace_file(), extract_all()) with automated ISO 9660 sector reallocation and extent management when replacing files that exceed their original sector capacity.
  • Added dedicated executable management helpers (get_boot_bin(), get_eboot_bin(), replace_boot_bin(), replace_eboot_bin()) for translation patching of BOOT.BIN and EBOOT.BIN.
  • Added deep texture scanning (find_textures()), traversing disc filesystem paths to identify and parse embedded GIMImage textures.
  • Enhanced CSOImage with to_iso_bytes() and introduced CSOCompressor for standalone pure-Python CSO compression and decompression.
  • Registered PSPRomHandler in RomManager with high precedence over generic ISO 9660 handlers.
  • Nintendo DS NSBMD 3D Model Engine (miorom.platforms.nds.nsbmd / miorom.platforms.nds):
  • Implemented analytical parser, builder, model hierarchy inspector, and bidirectional texture extraction/injection engine for official Nintendo DS 3D model containers (.nsbmd / BMD0 & MDL0) with 100% zero external dependencies (NSBMDFile, NSBMDModel, NSBMDMaterial, BMD0HeaderStruct, MDL0HeaderStruct, create_synthetic_nsbmd).
  • Implemented declarative low-level binary headers (BMD0HeaderStruct, MDL0HeaderStruct) strictly via miorom.core.schema.BinaryStruct ensuring unified struct processing without raw struct operations.
  • Added object-oriented model and material hierarchy inspection (NSBMDModel, NSBMDMaterial), extracting model dictionaries, bone nodes, and material-to-texture/palette linkages.
  • Implemented bidirectional texture bridge with NSBTXFile, adding export_nsbtx() / export_nsbtx_file() to dump embedded TEX0 sections to standalone .nsbtx files, and import_nsbtx() / replace_texture() to inject custom/translated textures without disturbing 3D display list bytecode or bone meshes.
  • Added texture stripping (strip_textures()), seamlessly converting 2-block containers into single-block MDL0-only models and downsizing the block offset table.
  • Added relational texture auditing (validate_textures()), verifying whether all textures and palettes referenced across materials are present in the embedded TEX0 block.
  • Enhanced NSBTXFile with from_tex0_bytes() and to_tex0_bytes() for modular texture block parsing and serialization without code duplication.
  • Integrated directly into NDSRom (get_model(), set_model()).
  • Nintendo DS Cartridge Banner Engine (miorom.platforms.nds.banner / miorom.platforms.nds):
  • Implemented analytical parser, builder, image converter, and multilingual title editor for official Nintendo DS banner containers (banner.bin) with 100% zero external dependencies (NDSBanner, NDSBannerHeaderStruct, bgr555_to_rgb, rgb_to_bgr555).
  • Implemented declarative 32-byte binary header (NDSBannerHeaderStruct) strictly via miorom.core.schema.BinaryStruct ensuring unified struct processing without raw struct operations.
  • Added 32x32 4bpp tiled icon bitmap decoding and encoding (get_pixel_indices(), set_pixel_indices()), converting between 16 hardware 8x8 pixel tiles (arranged in a 4x4 tile grid) and 2D index arrays.
  • Added 16-color BGR555 palette parsing and serialization, strictly treating color index 0 as transparent background (alpha = 0) and indices 1..15 as opaque colors (alpha = 255).
  • Added bidirectional image bridge (to_image(), to_rgba_bytes(), set_image()), rendering icons to RGBA PIL Image and quantizing 32x32 user images into optimal 16-color palettes with Euclidean color distance matching in 15-bit RGB space.
  • Added multilingual title management supporting up to 8 languages (get_title(), set_title() for Japanese, English, French, German, Italian, Spanish, Chinese, Korean) with even-aligned UTF-16LE null termination and multiline newline (\n) handling.
  • Added automatic format version escalation (promoting version 1 to version 2 on Chinese title addition, and version 3 on Korean title addition).
  • Added bit-exact multi-range CRC16 recalculation and validation (is_crc_valid(), recalculate_crc()) computing crc_v1 (covering 0x0020..0x083F), crc_v2 (0x0020..0x093F), and crc_v3 (0x0020..0x0A3F) via RetroChecksum.crc16_modbus().
  • Integrated directly into NDSRom (get_banner(), set_banner(), get_banner_title()).
  • Nintendo DS Bottom LZ (BLZ / Backwards LZ77) Compression Engine (miorom.compression.blz / miorom.compression):
  • Implemented bit-exact analytical decompressor, compressor, format detector, and metadata trailer parser for Nintendo DS Bottom LZ (BLZ / Backwards LZ77) binaries with 100% zero external dependencies (BLZ, BLZTrailerStruct).
  • Implemented low-level declarative binary structure for the 8-byte trailer (BLZTrailerStruct) via miorom.core.schema.BinaryStruct ensuring unified struct processing without raw struct operations.
  • Added support for backwards LZ77 in-place RAM decompression (BLZ.decompress()), unpacking literals and 2-byte compressed tokens (displacement 3..4098, length 3..18) from inverted buffers with automatic payload inversion.
  • Added high-performance pure-Python LZ77 compressor (BLZ.compress()) with n-gram prefix hash table chaining for accelerated sliding window match finding across large executable binaries.
  • Added ARM9 Secure Area preservation mode (is_arm9=True), keeping the initial 16KB (0x4000) Secure Area uncompressed and automatically calculating/updating the 2KB Secure Area CRC16 at offset 0x0E via RetroChecksum.crc16_modbus().
  • Added automatic fallback for uncompressible binary streams, formatting output with 4-byte alignment padding and a 4-byte zero trailer compliant with CUE BLZ and official Nintendo SDK conventions.
  • Registered blz codec in miorom.compression registry for auto-detection and named dispatch (compress(data, fmt="blz"), decompress(data)).
  • Nintendo DS ARM9 & Overlay BLZ Integration (miorom.platforms.nds.rom / miorom.platforms.nds.overlay):
  • Enhanced NDSRom with ARM9 compression diagnostics and decompression utilities (is_arm9_compressed(), decompress_arm9(), compress_arm9(), and set_arm9_binary()), providing seamless ARM9 code modification and repacking workflows for translators.
  • Enhanced NDSOverlayCompressor in miorom.platforms.nds.overlay to automatically detect, decompress, and compress overlay payloads (y9.bin/y7.bin) using either standard BIOS LZ10 or BLZ backwards LZ77 (NDSOverlayCompressor.is_compressed(), decompress(), compress(codec="blz")).
  • Nintendo MSBF Dialogue Flowchart Engine (miorom.text.msbf / miorom.text / miorom.platforms.wii):
  • Implemented analytical parser, builder, graph visualizer, and bidirectional MSBT synchronization engine for Nintendo MSBF (.msbf, magic MsgFlwBn) dialogue flowchart and event logic files with 100% zero external dependencies (MSBFFile, FlowNode, MessageNode, ChoiceNode, EventNode, create_synthetic_msbf, MSBFHeaderStruct, MSBFSectionHeaderStruct, FLW3HeaderStruct, FLW3NodeStruct).
  • Implemented declarative low-level binary structures strictly via miorom.core.schema.BinaryStruct ensuring unified binary packing/unpacking and zero redundant struct logic.
  • Implemented high-level directed node graph model transforming raw 16-byte FLW3 nodes and branch table jump indices into intuitive object-oriented nodes (MessageNode for dialogue lines, ChoiceNode for branching player decisions, EventNode for script triggers).
  • Added bidirectional MSBT linking (link_msbt()) automatically cross-referencing message node indices with MSBTFile labels and dialogue text strings to provide human-readable text previews during flowchart analysis.
  • Added Markdown Mermaid flowchart diagram generation (to_mermaid()) rendering full conversation trees and branching decision paths directly into standard flowchart TD diagrams for visual story auditing.
  • Implemented lossless JSON dictionary serialization and deserialization (to_dict(), from_dict()) for automated script translation tools and dialog tree editors.
  • Added bit-exact binary serialization (to_bytes(), save()) with automatic branch table assembly, FEN1 entry points hash table generation, and 16-byte section padding.
  • Nintendo GameCube & Wii THP Video Cutscene Engine (miorom.platforms.wii.thp / miorom.platforms.wii):
  • Implemented analytical parser, demuxer, muxer, cutscene audio dubbing, and video frame replacement (hardsubbing) engine for official Nintendo THP video containers (.thp, magic THP\0) with 100% zero external dependencies (THPMovie, THPFrame, create_synthetic_thp, THPHeaderStruct, THPComponentHeaderStruct, THPVideoInfoStruct, THPAudioInfoStruct, THPFrameHeaderStruct, THPAudioFrameHeaderStruct).
  • Implemented declarative low-level binary structures strictly via miorom.core.schema.BinaryStruct ensuring unified struct processing and zero redundant parsing logic.
  • Added Motion JPEG video frame extraction and individual frame image replacement (get_frame(), export_frames(), replace_frame()), allowing modders and translators to extract clean video frames and burn translated text subtitles directly into video frames for hardsubbing.
  • Added multi-channel interleaved DSP-ADPCM cutscene audio demuxing (export_audio_wav()) seamlessly concatenating frame-by-frame audio slices into a single continuous 16-bit PCM WavSound stream.
  • Added zero-drift cutscene audio dubbing replacement (set_audio_wav()) employing high-precision fractional sample accumulators to interleave custom WAV audio across arbitrary cutscene frame counts without desync.
  • Implemented bit-exact movie remuxing (to_bytes(), save()) with automatic recalculation of max_buffer_size, max_audio_samples, and bidirectional frame linkages (prev_frame_size, next_frame_size).
  • Added full support for silent video-only movies (single video component).
  • Nintendo Wii BRSAR Sound Archive Engine (miorom.platforms.wii.brsar / miorom.platforms.wii):
  • Implemented analytical parser, builder, extractor, and automated cross-archive voice undubbing engine for Nintendo NW4R monolithic sound archives (.brsar, magic RSAR) with 100% zero external dependencies (BRSARSoundArchive, SoundEntry, UndubTransferReport, RWSDFile, create_synthetic_brsar, BRSARHeaderStruct, SYMBHeaderStruct, INFOHeaderStruct, FILEHeaderStruct, RWSDHeaderStruct, WSD1HeaderStruct, DATAHeaderStruct).
  • Added full relational parsing across all archive sections (RSAR header, SYMB symbol table, INFO parameter tables, and FILE payload section), resolving developer symbol strings to internal Sound IDs and File IDs.
  • Implemented analytical RWSDFile (Revolution Wave Sound Data) sub-file handler parsing WSD1 wave descriptors and DATA audio blocks with DSP-ADPCM and 16-bit uncompressed PCM codecs.
  • Added bi-directional WAV audio bridge (export_wav(), import_wav()) allowing audio modders and translators to extract voice clips and sound effects directly to standard 16-bit PCM WavSound / WAV files, and replace voice lines with custom WAVs automatically encoded into hardware DSP-ADPCM.
  • Added automated 32-byte (0x20) DMA-compliant payload alignment and relational offset/size recalculation across RSARHeaderStruct, INFOHeaderStruct, and the FILE section.
  • Implemented one-click cross-archive voice undubbing engine (apply_undub_from()) to automatically match and transfer voice clips (e.g. VO_ prefix) from Japanese BRSAR archives into USA/European BRSAR archives while keeping sound effects and musical sequences untouched.
  • Nintendo Wii & GameCube Opening Banner Engine (miorom.platforms.wii.banner / miorom.platforms.wii):
  • Implemented analytical parser, builder, and multimedia extractor for Nintendo Wii and GameCube opening banner containers (opening.bnr / 00000000.app) with 100% zero external dependencies (WiiBanner, GCBanner, BannerFile, IMETHeaderStruct, IMD5HeaderStruct, GCBannerHeaderStruct, GCCommentStruct, BNSHeaderStruct).
  • Added full support for Wii IMET headers across 10 official languages (Japanese, English, German, French, Spanish, Italian, Dutch, Simplified Chinese, Traditional Chinese, Korean) with automatic UTF-16-BE string conversion and Nintendo MD5 checksum calculation.
  • Implemented IMD5 wrapper parsing and automatic re-wrapping with payload MD5 recalculation for meta/banner.bin, meta/icon.bin, and meta/sound.bin.
  • Added pure-Python BNS (Banner Nintendo Sound) audio decoder and encoder converting between BNS DSP-ADPCM / PCM16 streams and standard WAV format (get_sound_wav(), set_sound_wav()).
  • Added GameCube BNR1 (single-language) and BNR2 (6-language) banner support with 96x32 RGB5A3 hardware micro-tile image decoding and encoding via PNGImage with alpha transparency.
  • Added unified factory BannerFile.from_bytes() with automatic format discrimination between Wii and GameCube banners.
  • Integrated directly into WiiDisc (get_banner(), set_banner()) and WiiRomHandler (extracting sys/opening.bnr on unpack and re-injecting during disc repacking).
  • Nintendo Wii BRLYT UI Layout Engine (miorom.platforms.wii.brlyt / miorom.platforms.wii):
  • Implemented dual-layer analytical parser, tree hierarchy reconstructor, and surgical mutation engine for Nintendo NW4R BRLYT (.brlyt, magic RLYT) user interface and HUD layout containers (BRLYTFile, BasePane, Pane, PicturePane, TextBoxPane, GenericPane, BRLYTHeaderStruct, BRLYTLyt1Struct, BRLYTPaneStruct, BRLYTPic1Struct, BRLYTTxt1Struct, BRLYTResourceListHeaderStruct).
  • Added full tree hierarchy reconstruction parsing pas1 (Pane Start / push) and pae1 (Pane End / pop) delimiter chunks into structured parent-child node graphs with pre-order traversal (iter_tree(), find_pane(), find_text_boxes()).
  • Implemented rich text manipulation for txt1 panes with UTF-16-BE string encoding, dynamic RAM buffer capacity expansion (str_buf_len), and buffer overflow protection (expand_buffer=False guard).
  • Added automated font list (fnl1) and texture list (txl1) string table parsing and rebuilding (parse_resource_list(), build_resource_list()).
  • Added font remapping (remap_font()) allowing translators to redirect text box font bindings from Japanese kanji fonts to custom Latin fonts (e.g. created via BRFNTFont) with automatic fnl1 table synchronization.
  • Implemented localization dictionary export/import (export_strings(), import_strings()) to streamline UI translation and script dumping workflows.
  • Preserved complete backward compatibility with legacy section-level primitives (parse_brlyt_sections, rebuild_brlyt, find_pane, update_pane).
  • Nintendo Wii BRFNT Font & TGLP Glyph Sheet Engine (miorom.platforms.wii.brfnt / miorom.platforms.wii):
  • Implemented analytical parser, builder, and font expansion engine for Nintendo NW4R BRFNT (.brfnt, magic RFNT) and Nintendo DS NFTR (.nftr, magic FONT) font containers (BRFNTFont, BRFNTHeaderStruct, BRFNTFinfStruct, BRFNTTglpStruct, BRFNTWidthHeaderStruct, BRFNTCharMapHeaderStruct, CharWidth).
  • Added multi-format Nintendo GX hardware texture decoding and encoding for font sheets supporting I4, I8, IA4, IA8, and RGBA8 micro-tile formats with zero external dependencies.
  • Implemented single glyph and full sheet PNG image extraction and surgical replacement (get_sheet_image(), export_sheets(), get_glyph_image(), inject_glyph_image()) via PNGImage with transparent alpha.
  • Implemented dynamic font expansion (add_character()) allowing modders and translators to inject accented Latin characters (é, è, ê, ô, ç, ñ), Indonesian letters, and custom controller button glyphs into unused sheet cells with automatic CMAP and CWDH registration.
  • Added deterministic binary serialization (to_bytes(), save()) and clean font instantiation (BRFNTFont.create()).
  • Nintendo Wii Riivolution XML Mod Engine (miorom.platforms.wii.riivolution / miorom.platforms.wii / miorom.patch):
  • Implemented analytical parser, serializer, automated diff packager, and virtual disc patcher for Nintendo Wii Riivolution mod configurations (.xml, <wiidisc>) with 100% zero external dependencies (RiivolutionFile, RiivolutionPatch, FileRedirect, FolderRedirect, MemoryPatch, RiivolutionOption, RiivolutionChoice, RiivolutionSection).
  • Added automated fast recursive directory diffing (create_riivolution_package()) using size checks and chunked SHA-256 streaming hashing to compare clean retail dumps against modded projects, isolating delta assets and generating ready-to-use SD card / Dolphin layouts (riivolution/{mod_name}.xml and {mod_name}/...).
  • Implemented in-memory virtual disc patching (apply_riivolution_to_disc()) applying <file>, <folder>, and surgical <memory> patches directly to decrypted WiiDisc partitions and main.dol executables, allowing modders to repack patched games into standalone .iso, .wbfs, or .rvz ROMs without physical consoles.
  • Added unified CLI subcommands in miorom patch: export-riivolution (diffing & package generation) and apply-riivolution (direct disc patching).
  • Dolphin RVZ / WIA Compressed Optical Disc Engine (miorom.platforms.iso.rvz / miorom.platforms.iso):
  • Implemented analytical parser, lazy chunk decompressor, and bit-exact builder for Dolphin WIA v2 / RVZ (.rvz, magic RVZ\x01 / WIA\x01) compressed optical disc images (RVZDisc, RVZFileHeadStruct, RVZDiscHeadStruct, RVZGroupStruct, RVZRawDataStruct).
  • Added multi-algorithm streaming decompressor supporting Zstandard (zstd), LZMA/LZMA2 (lzma), Bzip2 (bz2), raw uncompressed chunks, and sparse zero-chunk skipping (data_size == 0).
  • Implemented pure-Python Lagged Fibonacci PRNG padding generator ($f = \text{xor}, j = 32, k = 521$) and Dolphin RVZ packing byte-stream decoder to faithfully reconstruct uncompressed disc padding.
  • Implemented chunk-level LRU virtual disc streaming (read_at(virtual_offset, size)) maintaining strict memory bounds (< 16 MB RAM) across multi-gigabyte Wii DVD-5 / DVD-9 and GameCube disc images.
  • Integrated bidirectional RVZ support into WiiDisc (from_stream(), to_rvz(), save_rvz()), WiiRomHandler (unpack, repack(format="rvz")), and GameCubeRomHandler (unpack, repack(format="rvz")).
  • Added instant format discrimination in RomManager checking the 128-byte dhead header without decompressing disc payload, seamlessly routing Wii RVZs to WiiRomHandler and GameCube RVZs to GameCubeRomHandler.
  • Nintendo Wii ROM Handler & CLI Integration (miorom.rom.handlers.wii / miorom.rom):
  • Implemented WiiRomHandler connecting WiiDisc and WBFSDisc engines directly into RomManager and the unified CLI pipeline (miorom unpack, miorom pack).
  • Added automatic container format detection via WBFS magic (offset 0x00), Wii Disc magic 0x5D1C9EA3 (offset 0x18), and partition table validation at 0x40000.
  • Added structured extraction into sys/ (header.bin, boot.bin, bi2.bin, apploader.img, main.dol, ticket.bin, tmd.bin) and root/ (all FST virtual game assets), generating complete miorom.meta.json manifests.
  • Implemented bit-exact repacking from modified filesystem folders with automatic FST re-indexing, cluster AES-128 re-encryption, H0–H3 hash tree generation, and Trucha Bug null RSA-2048 fake-signing, supporting both .iso and sparse .wbfs output targets.
  • Nintendo Wii & GameCube BRSTM Audio Stream Engine (miorom.platforms.wii.brstm / miorom.platforms.wii / miorom.audio):
  • Implemented analytical parser, builder, decoder, and encoder for Nintendo NW4R BRSTM (.brstm, magic RSTM) multi-channel streaming audio containers used throughout Nintendo Wii and GameCube titles (BRSTMFile, BRSTMChannelInfo, BRSTMHeaderStruct, BRSTMHeadPart1Struct, encode_dsp_adpcm_channel).
  • Added pure-Python multi-channel interleaved DSP-ADPCM decoder reconstructing signed 16-bit PCM audio with 16 dual-tap linear prediction coefficients, initial history samples, and scale exponent unpackers.
  • Implemented pure-Python DSP-ADPCM audio encoder optimizing 16 scale factors and predictor coefficients per 8-byte (14-sample) frame with squared-error minimization and clamp protection.
  • Added bidirectional bridge with WavCodec and WavSound (to_wav(), BRSTMFile.from_wav()) allowing audio modders and translators to convert custom WAV music and voice lines into retail-ready looping BRSTM streams and vice versa.
  • Implemented in-memory loop point editor (set_loop(loop_start, total_samples)) and diagnostic inspection API (summary()) calculating exact audio duration and loop repetition periods.
  • Nintendo Wii BRLAN Layout Animation Engine (miorom.platforms.wii.brlan / miorom.platforms.wii):
  • Implemented analytical parser, builder, and mathematical timeline curve evaluator for Nintendo NW4R layout animation files (.brlan, magic RLAN) accompanying BRLYT layouts (BRLANFile, BRLANPaneAnim, BRLANTrack, BRLANKeyframe).
  • Added mathematical timeline curve evaluator (evaluate(frame: float)) supporting Step (constant), Linear, and cubic Hermite spline interpolation with smooth tangents for UI animations (translations, rotations, scale, dimensions, alpha transparency, vertex colors, and UV coordinate scrolling).
  • Implemented in-memory keyframe manipulation API (set_keyframe(), remove_keyframe()) and global animation duration scaling (scale_duration()) allowing translators and modders to speed up or expand UI transitions (e.g. dialogue box animations).
  • Added deterministic 4-byte and 16-byte aligned binary rebuilding (to_bytes(), save()).
  • Implemented direct interoperability with BRLYT layout panes (BRLYTPaneStruct, BRLYTPic1Struct).
  • Nintendo Wii Optical Disc & WBFS Partition Engine (miorom.platforms.wii.disc / miorom.platforms.wii):
  • Implemented pure-Python, zero-dependency reverse engineering engine for Nintendo Wii optical disc images (.iso / .wii) and sparse block containers (.wbfs) (WiiDisc, WiiPartition, WiiDiscHeader, WiiPartitionInfo, WBFSDisc).
  • Added streaming lazy cluster decryption (< 50 MB RAM usage even for full 8.5 GB dual-layer retail DVD-9 disc images) resolving 32 KB clusters on-demand.
  • Implemented physical Partition Information Table parsing at 0x40000 supporting multiple cryptographically isolated partitions (Data, Update, Channel).
  • Implemented AES-128 Title Key decryption via retail and Korean Wii Common Keys using aes128_cbc_decrypt.
  • Implemented 4-level hierarchical hash tree engine: 31 SHA-1 sub-hashes (H0) per 32 KB cluster, H1 group hashing (8 clusters = 160 bytes), H2 group hashing (64 clusters / 2 MB = 160 bytes), and H3 root table hashing with automatic TMD content record validation.
  • Implemented recursive depth-first FST (File System Table) builder, directory traversal, and on-demand file extraction (sys/boot.bin, sys/bi2.bin, sys/apploader.img, sys/main.dol, and nested files/ game assets).
  • Implemented in-memory file replacement and surgical injection (disc.partitions[0]["files/..."] = new_bytes), automatic FST layout re-indexing, and Trucha Bug fake-signing (null RSA-2048 digital signatures) on modified Tickets and TMDs.
  • Added bi-directional repacking to standard playable .iso and sparse .wbfs containers.
  • Nintendo Wii BRRES & TEX0 Resource Engine (miorom.platforms.wii.tpl / miorom.platforms.wii):
  • Implemented analytical parser, builder, and texture replacement engine for Nintendo's NW4R BRRES (Binary Revolution Resource) archive container (BRRESFile, TEX0Image, PLT0Palette, BresIndexGroup, BresIndexEntry), the universal resource package used across modern Wii games (Mario Kart Wii, Super Smash Bros. Brawl, Xenoblade Chronicles, The Legend of Zelda: Skyward Sword, etc.).
  • Implemented pure-Python bitwise Patricia trie dictionary builder and $O(k)$ search engine (BresIndexGroup.build(), find(), to_bytes(), from_bytes()) calculating exact character indices and bit comparison discriminators matching Nintendo NW4R specifications.
  • Reused centralized Nintendo GX hardware texture codecs in tpl.py (decode_gx_texture, encode_gx_texture) with zero duplication, supporting CMPR (S3TC/DXT1), RGB565, RGB5A3, RGBA8, I4, I8, IA4, IA8, CI4, and CI8 with 100% zero external dependencies.
  • Implemented 100% byte-exact preservation of non-texture sections (MDL0 3D models, CHR0 skeletal animations, CLR0, PAT0, SRT0, SHP0, SCN0) stored in other_sections, ensuring that editing textures never corrupts or destabilizes character models or animations.
  • Added deterministic 32-byte section alignment and relative PowerPC signed 32-bit bres_offset calculations.
  • Added bidirectional to_image(), to_png(), and from_image() support with automatic color quantization for CI4 and CI8 paletted textures.
  • Nintendo Wii WAD Package Engine & Direct U8 Bridge (miorom.platforms.wii.u8 / miorom.platforms.wii):
  • Implemented full parser, serializer, and surgical modifier for Nintendo Wii installable packages (WADFile, WADTicket, WADTmd, WADContentRecord), used for WiiWare, Virtual Console, Channels, and DLCs.
  • Implemented standalone pure-Python table-driven AES-128-CBC cipher (aes128_cbc_encrypt, aes128_cbc_decrypt) with 100% zero external dependencies, verified against NIST SP 800-38A standard vectors.
  • Enforced strict key safety without proprietary hardcoded keys: callers supply common_key: bytes or title_key: bytes on demand, while raw chunks remain fully inspectable without keys.
  • Implemented seamless in-memory bridge between WAD contents (.app files) and U8Archive (wad.get_u8_archive(), wad.set_u8_archive()), allowing translators to directly browse, unpack, patch, and repack WiiWare game filesystems without temporary disk extraction.
  • Added automatic SHA-1 hash and size recalculation in TMD records, along with standard Trucha Bug RSA null-signature fake-signing (wad.to_bytes(fake_sign=True)).
  • Enhanced U8Archive with full mutable in-memory dictionary-like API (__init__, from_bytes(), to_bytes(), save(), __getitem__, __setitem__, keys(), values(), items()).
  • Sony PlayStation Portable (PSP) GIM Texture & VRAM Swizzle Engine (miorom.platforms.psp.pbp / miorom.platforms.psp):
  • Implemented analytical parser, builder, and zero-dependency surgical image modifier for Sony GIM (Graphic Image Map) textures and micro-tiled PSP GE VRAM swizzle layouts (GIMImage, GIMFormat, GIMPixelOrder, psp_swizzle, psp_unswizzle).
  • Added pure-Python mathematical codec for PSP GE 16×8-byte (128-byte) micro-tile swizzling and unswizzling across 4bpp, 8bpp, 16bpp, and 32bpp textures with zero external dependencies.
  • Added support for all standard PSP GIM pixel formats: RGBA8888, RGBA5551, RGBA4444, RGBA5650, INDEX4 (16-color CLUT), and INDEX8 (256-color CLUT) with deterministic chunk serialization (to_bytes()) and bidirectional conversion to PNGImage / PIL Image.
  • Implemented surgical ROM hacking sub-region patching (patch_region()) and sub-region extraction (extract_region()) enabling translators to replace dialogue font characters or UI buttons with automatic, transparent unswizzling/re-swizzling and CLUT color matching.
  • Added embedded GIM texture scanning (find_gim_textures()) and icon helpers (get_icon(), set_icon()) in PBPFile.
  • Nintendo GameCube & Wii Relocatable Module (REL) Engine (miorom.link.dol / miorom.platforms.wii):
  • Implemented analytical parser, serializer, and surgical patcher for Nintendo dynamic executable modules (.rel) supporting format versions 1, 2, and 3 with zero external dependencies and 100% miorom.core.schema serializations.
  • Implemented state-machine relocation decoder and deterministic delta bytecode rebuilder with automatic R_RVL_NONE (201) pagination for offset gaps > 65,535 bytes to prevent 16-bit integer truncation in large data/string sections.
  • Added engineering analytical queries: find_relocations(), find_relocations_in_range(), find_references_to_symbol(), and summary().
  • Added surgical relocation shifting (shift_relocations()) allowing reverse engineers to insert expanded localized dialogue strings into data sections without breaking internal or cross-module pointer references.
  • Added non-destructive simulated PowerPC relocation resolution (simulate_resolve_section(), link_against_dol(), and DolBinary.resolve_rel()) to resolve 32-bit addresses (R_PPC_ADDR32, ADDR16_LO, ADDR16_HI, ADDR16_HA) and 24-bit relative branches (R_PPC_REL24, REL14) for disassembly with UniversalDisassembler.
  • Zero-Dependency RIFF/WAVE Audio Codec (miorom.audio.wav_codec) & Unified Audio CLI (miorom audio):
  • Implemented standalone WavCodec and WavSound container using purely miorom.core.schema with zero external dependencies. Supports encoding and decoding 8-bit unsigned, 16-bit signed, 24-bit PCM, and 32-bit IEEE float audio in mono or stereo.
  • Added seamless conversions: VAGFile.from_wav / .to_wav(), BRRCodec.from_wav / .to_wav(), DSPADPCMCodec.to_wav(), and centralized ADPCMCodec.build_wav / read_wav through WavCodec.
  • Added miorom audio CLI suite with convert (bidirectional transcoding between WAV, VAG, BRR, and DSP), extract (dumping WAVs from SPC audio RAM and SDAT sound archives), and info (inspecting sample rate, bit depth, channel layout, sample count, duration, and container headers with optional --json output).
  • Zero-Dependency PNG Codec & Standalone Image I/O (miorom.graphics.png_codec): Extracted and expanded the pure-Python PNG codec into a first-class module (PNGCodec, PNGImage, PNGColorType). Supports all PNG reconstruction filter algorithms (None, Sub, Up, Average, Paeth), multiple color modes (Grayscale, RGB, RGBA, Indexed with PLTE, Grayscale+Alpha with tRNS), and sub-byte bit depths (1, 2, 4, 8).
  • Zero-Dependency Graphics Fallbacks across All Platform Formats: Unblocked image export/import without Pillow across ImageBridge, TIMImage (PSX), TPLFile (Wii/GC), BTIImage (Wii/GC), NCERFile (NDS), NSBTXFile (NDS), GlyphBank, and MemoryCard (PSX). Added dedicated .to_png() / save_icon_png() methods to enable seamless extraction and insertion of textures, tiles, fonts, and icons directly into PNG files even in strictly zero-dependency environments.
  • Unified CLI Patching Suite & Patch Inspection (miorom patch): Added hierarchical miorom patch subcommands (apply, create, inspect) supporting BPS, IPS, PPF (v1/v2/v3), UPS, and XDelta with automated format detection via header magic bytes and extension fallback. Added standalone inspect_patch() and .inspect() methods across patchers to diagnose target sizes, CRC32 checksum validity, and record counts without altering ROMs.

Changed

  • Unified Binary Serialization via miorom.core.schema: Fully deprecated and eliminated raw Python struct imports across all 114 engine modules in src/miorom. All binary deserialization, packing, bytefield definitions, and endian-aware operations are now routed natively through miorom.core.schema (BinaryStruct, U8, I8, U16, I16, U32, I32, U64, I64, Float32, Float64, schema.pack, schema.unpack, schema.unpack_from, schema.pack_into, schema.calcsize, schema.Struct, schema.error), achieving zero direct dependencies on raw struct outside of the core schema engine.

Fixed

  • Nintendo MSBT Text Engine MSBTFile — UTF-8 control tag \x0E string truncation on null arguments, ATR1 attribute section omission, unlabelled string drop, and non-destructive custom section preservation (miorom.text.msbt):
  • UTF-8 Dialogue Control Tag Truncation: In MSBTFile.from_bytes(), UTF-8 strings were read with sec_data.find(b"\x00", off). In official Nintendo MSBT format, control tags in UTF-8 begin with 0x0E, followed by a 6-byte header (group ID, tag ID, and argument length as uint16) and arg_len argument bytes. Whenever any parameter inside the control tag (e.g. font color, wait frames, player variables, speed) contained a zero byte (0x00), find() stopped prematurely at the null byte inside the control tag, truncating the sentence and permanently destroying all dialogue text after the tag. Fixed by parsing the control tag header and skipping 7 + arg_len bytes in UTF-8 mode, ensuring string parsing only terminates at authentic dialogue null delimiters.
  • ATR1 Attribute Section Omission & Dropped Message Attributes: MSBTEntry declares attributes: bytes = b"", but MSBTFile.from_bytes() completely ignored ATR1 sections, leaving all message attributes empty. Furthermore, to_bytes() hardcoded section_count = 2, dropping ATR1 and destroying any custom sections (e.g. TSY1 style tables, NLI1, ATO1), which caused games like Super Mario Galaxy and The Legend of Zelda to crash upon loading repacked MSBTs. Fixed by parsing ATR1 entries into MSBTEntry.attributes, generating ATR1 during to_bytes() whenever attributes are present, preserving all unrecognized sections in other_sections, and dynamically computing section_count = len(sections).
  • Unlabelled Dialogue String Loss: from_bytes() previously built entries solely by iterating over labels. If an MSBT file contained strings in TXT2 without corresponding labels in LBL1 (or if label entries were sparse), unlabelled strings were silently discarded, shifting subsequent string indices and breaking game dialog lookups. Fixed by mapping all strings $0 \le i < \text{str_count}$, resolving to existing labels or fallback STR_{i}. Added regression tests in tests/test_text_bmg_msbt.py.
  • Nintendo DS Cartridge ROM Header CRC-16 Checksum Conflict & RetroChecksum.crc16_nds (miorom.platforms.nds.rom, miorom.core.integrity, miorom.rom.handlers.nds, miorom.core.checksum):
  • Header Checksum Algorithm Desynchronization & Hardware Boot Failure: In src/miorom/platforms/nds/rom.py, calculate_nds_crc16() was erroneously implemented using reflected polynomial 0xA001 (CRC-16-IBM / Modbus). Official Nintendo DS hardware and ARM7/ARM9 BIOS Boot ROM SWI 0x0E verify the cartridge header at offset 0x15E covering bytes 0x000..0x15D using reflected polynomial 0x8408 (CRC-16-CCITT / Kermit). Consequently, NDSRom.verify_header_checksum() returned False on valid retail NDS ROMs, ROMs repacked by NDSRomHandler, and ROMs repaired by RomIntegrityManager. Moreover, NDSRom.fix_header_checksum() wrote the incorrect 0xA001 checksum into offset 0x15E, rendering the ROM unbootable on authentic DS hardware and emulators (DeSmuME, melonDS). Fixed by adding RetroChecksum.crc16_nds() (poly 0x8408, init 0xFFFF), unifying calculate_nds_crc16() across rom.py, handlers/nds.py, and integrity.py, and adding is_header_checksum_valid() to NDSRom. Added regression tests in tests/test_nds.py.
  • IPS Patcher IpsPatcher — Silent byte loss at offset 0x454F46 (b"EOF"), apply_stream overlapping hunks corruption, and truncation preservation (miorom.patch.ips):
  • Silent Byte Truncation on Offset Collision: When generating an IPS patch via IpsPatcher.create(), if a modified byte run started at offset 0x454F46 (the ASCII value of "EOF"), the code previously did start = 0x454F47; i = 0x454F47. This jumped over byte 0x454F46, silently omitting the user's change at that exact offset and leaving it unpatched. Fixed by stepping back 1 byte to start = 0x454F45 and prepending modified[0x454F45] to diff_bytes, safely bypassing the EOF collision without discarding byte 0x454F46.
  • apply_stream Overlapping Records Desynchronization & Truncation: In IpsPatcher.apply_stream(), hunks were sorted but not merged via merge_patches(). When an IPS patch contained overlapping records, current_pos < hunk.offset evaluated to False and output_stream.write(hunk.data) wrote at the current stream pointer without seeking back, shifting subsequent file data and corrupting the output stream. Additionally, apply_stream ignored the optional 3-byte truncation offset after EOF. Fixed by merging hunks with merge_patches() and strictly enforcing truncate_size. Added regression tests in tests/test_patch.py.
  • Multi-Track CD-ROM Engine CueBinDisc — Mode 2 Form 1 subheader and EDC corruption, Mode 1 Yellow Book EDC invalidity, and single-BIN multi-track desynchronization (miorom.platforms.cdrom.disc):
  • Mode 2 Form 1 CD-ROM Track Corruption: When replacing data in MODE2/2352 tracks (e.g. PS1, Sega Saturn), CueBinDisc.replace_track_data() hardcoded Mode 1 formatting (header mode = 0x01), omitted the mandatory 8-byte subheader (0x00 0x00 0x08 0x00 0x00 0x00 0x08 0x00), placed user data at byte 16 instead of byte 24 (causing an 8-byte shift that truncated the first 8 bytes of every sector), and wrote EDC at offset 2064 instead of 2072. Fixed by building standards-compliant Mode 2 Form 1 sectors with subheaders and EDC at offset 2072.
  • Mode 1 Yellow Book EDC Checksum Omission: In Mode 1, replace_track_data() calculated EDC over header + chunk (2052 bytes), omitting SYNC_PATTERN. According to ECMA-130 section 14.3, Mode 1 EDC is generated over bytes 0..2063 (2064 bytes) including the sync pattern. Fixed by calculating EDC over sector[:2064].
  • Multi-Track Single-BIN Index Desynchronization: When a data track was expanded/contracted in a single-BIN disc, subsequent audio and data tracks retained stale LBA indices in the CUE sheet, causing audio and data to read from misaligned offsets. Fixed by updating subsequent track indices by delta_sectors. Added regression tests in tests/test_platforms_cdrom.py.
  • Nintendo Wii ROM Handler WiiRomHandler — Disc Title ID specification violation and non-destructive in-memory banner staging (miorom.rom.handlers.wii):
  • Disc Title ID Specification Violation: In WiiRomHandler.repack(), the default Ticket and TMD Title ID was formatted as title_id=header.game_id[:4] + b"\x00"*4 (e.g. b"RMCE\x00\x00\x00\x00"), violating the official Wii Title ID specification where disc titles require prefix 00010000 (b"\x00\x01\x00\x00"). Fixed by formatting Title ID as b"\x00\x01\x00\x00" + header.game_id[:4].
  • Non-Destructive In-Memory Banner Staging: repack() previously wrote opening.bnr directly to root_dir/opening.bnr on disk, mutating the user's project files during repack. Fixed by staging part["files/opening.bnr"] in memory. Added regression tests in tests/test_rom_handlers_wii.py.
  • ISO 9660 Filesystem ISO9660 — Sector alignment guard on file extent relocation (miorom.platforms.iso.iso9660):
  • Sector Alignment on Append: In ISO9660.replace_file(), if an enlarged file was appended to an ISO whose length was not an exact multiple of 2048 bytes, target_lba calculated from len(self.data) // 2048 desynchronized from the actual appended byte offset. Fixed by padding self.data to a 2048-byte sector boundary before calculating target_lba and extending data. Added regression tests in tests/test_iso9660_builder.py.
  • Nintendo GameCube GameCubeRomHandler — RVZ unpack disc_base.bin omission producing unbootable repacked discs, extraction of sys/main.dol, and modified executable & header injection during repack (miorom.rom.handlers.gc):
  • RVZ Unpack disc_base.bin Omission: In GameCubeRomHandler.unpack_rvz(), sys/header.bin and virtual files in root/ were extracted, but sys/disc_base.bin was omitted. When repacking from this directory, repack() failed to find disc_base.bin and fell back to synthesizing a 0x450000-byte zeroed disc base, wiping out the apploader, bi2.bin, and the executable main.dol, producing an unbootable disc. Fixed by extracting sys/disc_base.bin up to fst_offset directly from the RVZ disc stream.
  • Extraction of sys/main.dol: In unpack(), unpack_file(), and unpack_rvz(), sys/main.dol was never extracted as a standalone file, impeding disassembly, reverse engineering, and ASM hacks. Added automated detection and extraction of sys/main.dol using DolBinary to calculate exact text/data section bounds.
  • Repack Injection of Modified sys/main.dol & sys/header.bin: repack() previously ignored changes made to sys/main.dol and sys/header.bin whenever disc_base.bin was present. Enhanced repack() to detect updated sys/header.bin (refreshing disc.header and sector 0) and updated sys/main.dol (injecting the patched binary at dol_offset and safely elevating fst_offset if the executable expanded). Added regression tests in tests/test_platforms_gc.py and tests/test_platforms_iso_rvz.py.
  • Sony PlayStation 1 & PSP PSXRomHandler and PSPRomHandler — TypeError crash on filepath unpack, signature alignment, and filesystem clue format detection (miorom.rom.handlers.psx, miorom.rom.handlers.psp, miorom.rom.manager):
  • TypeError on Filepath Unpack: PSXRomHandler.unpack() and PSPRomHandler.unpack() previously declared signatures (self, data: bytes, output_dir: str) without **kwargs. When unpacked via RomManager.unpack(filepath, ...) or CLI miorom unpack, filepath=filepath was passed as a keyword argument, raising TypeError: unpack() got an unexpected keyword argument 'filepath' and crashing execution. Fixed by adding **kwargs to both handlers and leveraging PSXRom.from_file() / PSPRom.from_file() when streaming from disk.
  • Repack First Parameter Alignment & Format Inference Clues: Aligned the first parameter of repack() to input_dir matching BaseRomHandler, and added filesystem detection clues in RomManager.repack() for SYSTEM.CNF (psx), PSP_GAME (psp), and ticket.bin/tmd.bin (wii) to prevent unpacked directories from misidentifying as "u8". Added regression tests in tests/test_platforms_psx_rom.py and tests/test_platforms_psp_rom.py.
  • Nintendo GameCube, Wii, & DS BMGFile — Escape sequence text truncation on null parameters, and INF1 attributes & header parameter data loss (miorom.text.bmg):
  • Escape Sequence Text Truncation: In official Nintendo BMG files, dialogue escape/control sequences begin with 0x001A (in UTF-16BE) or 0x1A (in 8-bit encodings CP1252/Shift-JIS/UTF-8), followed by a 1-byte total sequence length. Parameters inside the escape sequence (e.g. RGB color 0, 0, 0, 255, player index 0, font index 0) frequently contain zero bytes (0x00 / 0x00 0x00). BMGFile.from_bytes() previously searched naively for null terminators with b"\x00\x00" or b"\x00", immediately truncating the dialogue string inside the escape sequence and destroying the remainder of the sentence. Fixed by parsing the escape code length and skipping the escape sequence body to preserve all text following formatted tags.
  • INF1 Message Attributes & Header Parameters Data Loss: In BMGFile.from_bytes(), inf_hdr.file_id and inf_hdr.default_color were dropped, and message attributes (sec_reader.skip(inf_hdr.entry_size - 4)) were discarded, leaving all messages with empty attributes = b"". In BMGFile.to_bytes(), entry_size was hardcoded to 8, corrupting games that use entry_size = 4 (no attributes) or entry_size = 12 (Twilight Princess with 8-byte attributes), and attributes were replaced with zeroes (inf_body.write_u32(0)). Fixed by capturing file_id, default_color, and inf_entry_size, reading and populating BMGMessage.attributes, and dynamically writing preserved or custom attributes. Added regression tests in tests/test_text_bmg_msbt.py.
  • Sega Mega Drive / Genesis MDHeader & MDRom — Backup RAM / SRAM mapping address erasure and configuration (miorom.platforms.md.rom):
  • SRAM Mapping Address Erasure: In Sega Genesis / Mega Drive cartridges, offset 0x01B0..0x01EF (64 bytes) defines the battery backup RAM (SRAM) configuration (start address 0x00200001, end address 0x00200FFF, backup RAM flags 0xF8 0x20, and memo fields). MDHeader.parse() previously checked only sram_support = _reserved_0xB0[:2] == b"RA" and discarded the entire 64-byte block. When saving or packing (MDHeader.pack() / MDRom.to_bytes()), reserved was allocated as zeroes with only b"RA" at the start, completely wiping out all SRAM start/end addresses and flags. This prevented saving from functioning in emulators, flashcarts, and hardware after editing with MDRom. Fixed by storing reserved_b0: bytes, preserving all 64 bytes of SRAM settings across roundtrips, adding sram_start and sram_end properties, and providing configure_sram() for non-SRAM ROMs. Added regression tests in tests/test_platforms_md.py.
  • Nintendo GameCube & Wii GCBanner, WiiBanner, & GCCommentStruct — European BNR2 character degradation, RGB5A3 buffer overrun crash, and UTF-16 surrogate preservation (miorom.platforms.wii.banner):
  • European BNR2 Multilingual Character Degradation to '?': In GameCube European PAL disc releases (BNR2), 6 language slots are defined (English, German, French, Spanish, Italian, Dutch), all formatted in Windows-1252 (CP1252). GCCommentStruct hardcoded encoding="shift-jis" for title, company, and description fields. When saving European accented characters (e.g. Pokémon, Über, Español, Édition), Shift-JIS encoding failed and replaced every accented Latin character with ? (e.g. Pok?mon, ?ber). Fixed by implementing dynamic dual-encoding support in GCCommentStruct, defaulting to CP1252 for BNR2 and Shift-JIS for BNR1 with automatic fallback, and adding character-boundary safe truncation.
  • RGB5A3 Encoding Buffer Overrun: In encode_rgb5a3(), pixel extraction did not verify buffer length before indexing rgba[idx], causing unhandled IndexError when images with custom or smaller dimensions were supplied. Added explicit buffer length guards and enhanced GCBanner.set_image() to accept PNGImage, Image.Image, raw bytes, and file paths with Pillow-based automatic resizing.
  • Wii Opening Banner Language Aliases & Safe UTF-16 Truncation: Added language alias resolution (WII_LANGUAGE_ALIASES) to WiiBanner.get_title() and set_title() allowing human-readable names ("japanese", "english", "ja") and integer indices (0..9), and updated to_bytes() to truncate oversized UTF-16 strings along character boundaries rather than slicing raw bytes. Added regression tests in tests/test_platforms_wii_banner.py.
  • Nintendo DS NDSBanner & NDSRomHandler — Opaque black palette index 0 transparency hole, multilingual title truncation on extraction, invalid language index bounds, and auto-elevation (miorom.platforms.nds.banner, miorom.rom.handlers.nds):
  • Opaque Black Palette Index 0 Hardware Transparency Bug: In NDSBanner.set_image(), the quantizer mapped pixel colors to palette indices using if rgb_q in palette[1:]: pixel_indices[y][x] = palette.index(rgb_q). Because palette always stores (0, 0, 0) at index 0 (the hardware transparent color on NDS), any opaque black pixel (0, 0, 0, 255) (such as outlines, text, or pupils) evaluated palette.index((0, 0, 0)) to 0. Consequently, all opaque black pixels were assigned index 0, turning completely transparent (alpha = 0) on hardware, emulators, and PNG exports. Fixed by querying chosen_opaque directly and assigning 1 + chosen_opaque.index(rgb_q) for opaque pixels, strictly restricting index 0 to transparent pixels (alpha < 128).
  • NDSRomHandler.unpack() Multilingual & DSi Banner Truncation: In NDSRomHandler.unpack(), extracting banner.bin hardcoded 0x840 bytes (data[rom.banner_offset : rom.banner_offset + 0x840]). For v2 (Chinese, 0x940 bytes), v3 (Korean, 0xA40 bytes), and DSi (0x23C0 bytes) ROMs, this truncated the banner file, destroying extended titles and animated icons when unpacking and repacking ROMs. Fixed by inspecting banner version to extract the full b_size (0x840 for v1, 0x940 for v2, 0xA40 for v3, 0x23C0 for DSi).
  • Multilingual Title Version Auto-Elevation: In NDSBanner.to_bytes(), if a banner was initialized with Chinese (language 6) or Korean (language 7) directly in the titles dictionary with version=1, num_langs remained 6, silently omitting the Chinese and Korean titles from the binary output. Fixed by checking self.titles to automatically elevate num_langs to 7 or 8 and bump self.version to 2 or 3.
  • Language Index Bounds & Safe UTF-16 Truncation: Added strict bounds checking (0 <= idx <= 7) to _resolve_language_index() to prevent invalid language keys from corrupting internal dictionaries, and updated to_bytes() title serialization to truncate oversized strings along character boundaries rather than slicing raw bytes. Added regression tests in tests/test_platforms_nds_banner.py.
  • Sony PlayStation 1 PSXMemoryCard & PSXSaveFile — Shift-JIS title 32-byte truncation, multi-block directory entry standard violations, payload expansion truncation, and delete safety (miorom.platforms.psx.memory_card):
  • Shift-JIS Title Truncation to 32 Bytes: In official Sony PlayStation 1 save games, the save header title field at Frame 0 offset 0x04..0x43 allocates 64 bytes for Shift-JIS title text. PSXSaveHeaderStruct previously divided this region into title_raw = RawBytes(32) and _reserved_title_pad = RawBytes(32). When reading, _parse_save_content only decoded title_raw (ignoring bytes 33..64), and build_block_data() truncated encoded titles at [:32], actively destroying long Japanese game titles (e.g. ファイナルファンタジーVII クラウド レベル99 at 43 bytes was truncated to ファイナルファンタジーVII クラウ). Fixed by declaring title_raw = RawBytes(64) in PSXSaveHeaderStruct and supporting full 64-byte Shift-JIS titles across _parse_save_content, build_block_data(), and .mcs single-save file export/import.
  • Multi-Block Directory Entry Specification Compliance: In PSXMemoryCard.inject_file(), file_size and filename were previously written to every block directory entry in a multi-block save chain. According to the Sony PS1 memory card specification, only the initial block directory entry (0x51 / IN_USE_INITIAL) may contain file_size and filename; middle blocks (0x52 / IN_USE_MIDDLE) and last blocks (0x53 / IN_USE_LAST) must zero out file_size and leave filename empty (b""). Writing non-zero file sizes and filenames on chained blocks caused potential directory corruption and parsing abnormalities in BIOS and emulators. Fixed by setting file_sz = 0 and fname_bytes = b"" for middle and last blocks.
  • Dynamic Payload Expansion & Block Recalculation: When save.payload was modified to be larger after creation, build_block_data() truncated data to the old self.block_count * BLOCK_SIZE without expanding blocks, silently dropping user data. Fixed by automatically recalculating needed_blocks and updating self.block_count and self.file_size = actual_file_size on serialization.
  • Delete Safety & Infinite Loop Protection: Enhanced delete_file() with block index bounds checks (1 <= start_block <= 15), validation that start_block is an initial block (PSXBlockState.IN_USE_INITIAL), and visited cycle protection to prevent infinite loops on corrupted card directory chains.
  • Convenience Query Methods: Added get_file(filename) and has_file(filename) to PSXMemoryCard for case-insensitive save retrieval. Added regression tests in tests/test_platforms_psx_memory_card.py.
  • Sony PSP PSPRom — PBP to ISO/CSO conversion failure, raw PBP payload leak, unmounted DATA.PSAR filesystem, and substring filename matching (miorom.platforms.psp.rom):
  • PBP to ISO/CSO Conversion Failure & Raw PBP Payload Leak: In PSPRom.to_bytes(), the serializer previously assigned iso_data = bytes(self._iso.data) if self._iso else (self._pbp.to_bytes() if self._pbp else b""). When converting a PBP package to ISO (rom.to_bytes(fmt=PSPFormat.ISO)) or saving with .iso extension (rom.save("game.iso")), self._iso was None, causing to_bytes() to return self._pbp.to_bytes(). This resulted in writing raw \x00PBP container bytes into .iso files instead of an ISO 9660 filesystem, causing mounting failures in PSP emulators. Furthermore, converting a PBP to CSO compressed the PBP bytes into a corrupt CSO. Fixed by implementing _build_iso_from_pbp() to cleanly extract the embedded ISO image from DATA.PSAR or synthesize a valid ISO 9660 disc image for homebrew PBPs with DATA.PSP and metadata.
  • Unmounted Embedded ISO Filesystem in PBP: In PSPRom.__init__, loading a PBP container initialized self._pbp but never checked DATA.PSAR for an embedded ISO 9660 image, leaving self._iso None. As a result, list_files() only returned top-level PBP section names instead of virtual game assets (PSP_GAME/...), and has_file() / get_file() failed to read game files from official PSN PBP releases. Fixed by detecting ISO 9660 PVD magic (\x01CD001) in DATA.PSAR and mounting self._iso = ISO9660(psar_data).
  • Bidirectional Synchronization on Modification: Enhanced replace_file(), set_title(), set_icon(), set_background(), and set_boot_audio() to automatically update both the PBP sections and the embedded DATA.PSAR ISO image when both are active.
  • Subdirectory Filename Matching Boundary Bug: In _find_iso_file(), the search loop previously matched via f.strip("/").upper().endswith(target). In games where an asset shared a suffix with a system file (e.g. REBOOT.BIN and BOOT.BIN), _find_iso_file("BOOT.BIN") erroneously returned the wrong file. Fixed by enforcing path component boundary checks (clean_f == target or clean_f.endswith("/" + target)). Added regression tests in tests/test_platforms_psp_rom.py.
  • Sony PlayStation 1 PSXRom — Mode 1 2352-byte raw CD-ROM BIN sector misalignment, PVD sector 16 offset detection, repacking format preservation, and dynamic SYSTEM.CNF cache desync (miorom.platforms.psx.rom):
  • Mode 1 2352-Byte Sector User Data Slicing Misalignment: In _bin_to_iso_bytes(), the sector payload extraction hardcoded sec[24:2072] (which corresponds to CD-ROM XA Mode 2 Form 1 layout containing an 8-byte subheader). Raw 2352-byte Mode 1 CD-ROM images (where sec[15] == 0x01) do not possess an 8-byte subheader, and store user data at sec[16:2064]. Slicing Mode 1 BIN sectors at offset 24 shifted every sector's data by 8 bytes, corrupting the ISO 9660 filesystem image and causing ISO9660 mounting to fail (ParseError: Failed to mount PlayStation 1 ISO 9660 filesystem). Fixed by inspecting mode = sec[15] per sector and extracting sec[16:2064] for Mode 1 and sec[24:2072] for Mode 2 Form 1.
  • Mode 1 PVD Detection at Sector 16: In _detect_sector_size_and_mode(), the detector only checked sec16_off + 24 for ISO_PVD_MAGIC. In Mode 1 raw BIN images, the PVD is located at sec16_off + 16. Updated detection logic to check both sec16_off + 24 (Mode 2 Form 1) and sec16_off + 16 (Mode 1), returning (2352, 1) and setting PSXFormat.BIN_MODE1.
  • Mode 1 Packaging and Repacking: Added PSXFormat.BIN_MODE1, implemented _iso_to_bin_mode1_bytes() with sync patterns, BCD MSF headers, Mode 1 sector layout, and bit-exact 32-bit EDC checksum calculation (calculate_cdrom_edc), and updated to_bytes(), generate_cue() (TRACK 01 MODE1/2352), and save() to correctly serialize and preserve Mode 1 format.
  • Dynamic SYSTEM.CNF Cache Desynchronization: In PSXRom.replace_file(), replacing SYSTEM.CNF with updated configuration (e.g. modified boot executable or game ID) updated the virtual ISO filesystem but left self.game_id, self.boot_path, self.region, and self.system_cnf cached with stale values. Fixed by dynamically calling self._parse_system_cnf() whenever SYSTEM.CNF is replaced. Added regression tests in tests/test_platforms_psx_rom.py.
  • GameCube/Wii RVZDisc & PSP PRXModule — RVZ 4-byte padding buffer mismatch, stream resource leakage, PRX imported variables omission, and symbol name resolution (miorom.platforms.iso.rvz, miorom.platforms.psp.prx):
  • RVZ 4-Byte Buffer Padding Mismatch: In RVZDisc.create_from_stream(), the code previously attempted to pad the binary buffer to 4 bytes using bio.seek(total_rvz_size). In Python's io.BytesIO, seeking past EOF without writing does not expand the underlying buffer. Consequently, whenever the final chunk was not an exact multiple of 4 bytes, bio.getvalue() returned a buffer 1 to 3 bytes shorter than total_rvz_size, desynchronizing the file size from file_head.wia_file_size and violating Dolphin's WIA/RVZ container specification. Fixed by explicitly zero-padding bytearray(bio.getvalue()) to total_rvz_size.
  • RVZ Stream Resource Management & Hash Verification: Added close(), context manager support (__enter__ / __exit__), and is_hash_valid() to RVZDisc to verify SHA-1 checksums across file header and disc descriptor structs and prevent open file descriptor leaks.
  • PRX Imported Variables Omission & Deserialization: PRXModule._parse_imports() previously ignored stub_struct.var_count, leaving imp_lib.variables completely empty on any module importing external variables (e.g. errno, hardware registers). Fixed by parsing variable NID tables and vstub_table (present when stub_len >= 6), implementing find_import_variable(), and updating replace_import_nid() to support both functions and variables.
  • PRX Symbol Name Persistence: When finding or replacing import NIDs by symbol name, dynamically computed NIDs now register with PSPNIDResolver and persist human-readable symbol names on PRXImportFunction and PRXImportVariable instances.
  • PSP ATRAC3 / ATRAC3plus Audio Engine & Wii U8Archive — RIFF container size desync truncation, inactive cSampleLoops loop loss, negative start index crash, Extensible ATRAC3 GUID support, and U8 mapping parity (miorom.platforms.psp.at3, miorom.platforms.wii.u8):
  • RIFF Container Size Desynchronization & Audio Truncation: When set_loop(), remove_loop(), or replace_data() mutated audio.raw_data in memory, the 32-bit RIFF file size field at byte offset 4..7 and riff_header.file_size remained at their old lengths. Because AT3Audio.__init__ clips raw_data to riff_header.file_size + 8, passing audio.raw_data directly to downstream consumers or re-parsing without explicitly calling to_bytes() truncated the appended 68-byte smpl chunk or expanded audio frames. Fixed by introducing _sync_riff_size() and updating both container header bytes and riff_header.file_size automatically across all mutation methods.
  • Inactive cSampleLoops == 0 In-Place Loop Loss: When updating an existing smpl chunk that had cSampleLoops == 0 (e.g. sampler metadata without active loop points), set_loop() updated start and end offsets but left cSampleLoops at 0. Subsequent inspections or re-parses dropped loop_point because cSampleLoops < 1. Fixed by automatically updating cSampleLoops to 1 in set_loop().
  • Input Bounds Validation: Added explicit ValueError validation in set_loop() ensuring start_sample >= 0, end_sample > start_sample, and end_sample <= total_samples (when known), preventing low-level unsigned conversion errors or invalid loop points beyond the audio length.
  • WAVE_FORMAT_EXTENSIBLE ATRAC3 GUID Recognition: In _parse_format(), WAVE_FORMAT_EXTENSIBLE only checked for ATRAC3PLUS_GUID. Added recognition of the official ATRAC3 sub-format GUID ({E923AAB4-BB58-4477-AA77-03374014CD26}) to correctly identify extensible ATRAC3 streams with 1024 samples/frame.
  • Corrupt Sub-chunk Sizing Bounds Check: In _parse_chunks(), added bounds validation offset + 8 + csize <= limit to raise ParseError when a corrupted chunk header claims a size exceeding the container boundary.
  • U8Archive.get() Mapping Parity: Added .get(path, default=None) on U8Archive for consistent dictionary-like retrieval across all Nintendo archive classes.
  • Wii Riivolution Engine & WiiPartition — DolBinary missing import crash, sub-file offset redirect AttributeError, resize/create/recursive specification violations, and orig_file layout mismatch (miorom.platforms.wii.riivolution, miorom.platforms.wii.disc):
  • DolBinary Missing Import & Invalid Method Call in MemoryPatch: In apply_riivolution_to_disc(), memory patching imported from miorom.platforms.gc.dol import DolBinary. DolBinary does not exist in miorom.platforms.gc.dol (the module defines DolFile), causing an unhandled ImportError whenever applying memory patches to main.dol. Furthermore, it attempted to call dol.write_address(mem.offset, mem.value), which does not exist on either class, and completely omitted verifying mem.original bytes against current RAM before writing. Fixed by importing DolFile, verifying mem.original via dol.read_memory() prior to patching, applying writes via dol.write_memory(), and updating partition.main_dol with dol.to_bytes().
  • WiiPartition.get() Missing Dict Method & Offset Redirect Crash: When applying FileRedirect with offset > 0, apply_riivolution_to_disc() called partition.get(norm_disc_path, b""). Because WiiPartition only implemented __getitem__ without a .get() method, partial file redirections raised AttributeError: 'WiiPartition' object has no attribute 'get'. Fixed by implementing .get(path, default=None) on WiiPartition for safe dictionary-like retrieval.
  • resize="false" and create="false" Specification Compliance: apply_riivolution_to_disc() previously ignored both f_redirect.resize and f_redirect.create (as well as f_folder.create). Fixed by enforcing that create="false" redirects do not create new files on the disc, and resize="false" truncates oversized replacements or zero-pads undersized replacements to preserve original file lengths.
  • FolderRedirect.recursive="false" Traversal Bug: When recursive="false", os.walk(src_folder) previously traversed all nested subdirectories. Fixed by clearing dirnames during traversal when recursive=False, restricting folder mapping strictly to top-level files.
  • Unpacked Directory Hierarchy Asymmetry in create_riivolution_package(): When original_root unpacked assets with root/ or files/ but modified_root unpacked flat (or vice versa), orig_file = orig_path / rel_path failed to find the original file and erroneously flagged all files as newly added. Fixed by checking candidate path fallbacks (rel_path, disc_rel_path, files/disc_rel_path, root/disc_rel_path).
  • THPMovie — Stereo DSP-ADPCM audio slice desynchronization, 80-byte header omission, and max_buffer_size buffer overflow (miorom.platforms.wii.thp):
  • Stereo DSP-ADPCM Audio Header & Decoding Corruption: In authentic Nintendo GameCube & Wii THP cutscenes (e.g. Super Smash Bros Brawl, Super Mario Galaxy, The Legend of Zelda: Twilight Princess, Mario Kart Wii), audio is standard stereo (channels == 2) stored using an 80-byte (0x50) slice header (channel_size, num_samples, coefs1 [32 bytes], coefs2 [32 bytes], and 4 history samples), followed by Left ADPCM data and Right ADPCM data (channel_size * 2 bytes total). THPMovie.export_audio_wav() previously only supported a mono 44-byte (0x2C) header. On authentic stereo cutscenes, it sliced audio starting from offset 44 (which contains Channel 2 coefficients and history samples), interpreting predictor coefficients as compressed ADPCM and corrupting the beginning of every frame with loud screeching noise, while completely dropping Channel 2. Furthermore, set_audio_wav() forcibly downmixed stereo audio to mono. Fixed by implementing THPStereoAudioFrameHeaderStruct (80 bytes), demuxing and decoding both Left and Right channels with their respective coefficient tables in export_audio_wav(), and preserving/encoding true stereo audio in set_audio_wav().
  • max_buffer_size Frame Header Sizing & Console Buffer Overflow: In THPMovie.to_bytes(), buf_size was previously computed as len(f.image_data) + len(f.audio_data), omitting the 16-byte THPFrameHeaderStruct. According to the Nintendo THP format specification and DVD streaming architecture, max_buffer_size specifies the maximum buffer in bytes required to store one complete frame slice (header + video + audio) allocated in console memory by THPPlayerInit. Omitting the 16 bytes produced undersized buffer allocations resulting in 16-byte buffer overflows on console hardware. Fixed by computing buf_size = THPFrameHeaderStruct.sizeof() + len(f.image_data) + len(f.audio_data) (i.e. max(frame_sizes)). Added regression tests in tests/test_platforms_wii_thp.py.
  • BNS Audio Engine — authentic codec 0 DSP-ADPCM silent decode fallback & Channel Info 12-byte struct layout desynchronization (miorom.platforms.wii.banner):
  • Authentic Codec 0 Discarding Audio: In authentic Nintendo BNS (Banner Nintendo Sound) files across all official Wii titles, the codec field at offset 0 of the INFO chunk is 0 (denoting standard DSP-ADPCM), whereas extended format variants occasionally use 2. decode_bns_to_wav() previously checked only elif codec == 2:, falling through to the else: branch which synthesized 1000 samples of silence. When reading opening banner sounds from retail Wii discs, all audio was discarded. Fixed by recognizing both standard 0 and 2 as DSP-ADPCM.
  • Channel Info 12-Byte Descriptor & DSP Coefficient Offset Desync: In the Nintendo BNS format specification (and standard decoders such as vgmstream), the Channel Info Pointer Table at offset 0x18 points to 12-byte Channel Info descriptors (channel_data_offset, channel_dsp_offset, reserved=0). encode_wav_to_bns() previously omitted the 12-byte Channel Info descriptors and placed raw coefficient blocks directly, while writing hardcoded offsets that were shifted by 4 bytes on mono streams. When parsed by standard decoders like vgmstream, this caused immediate parsing failures or misaligned DSP predictor coefficients. Furthermore, decode_bns_to_wav() hardcoded coefficient lookups to 0x20 + ch * 0x30 without following the Channel Info pointer table. Fixed by formatting full 12-byte Channel Info structures in encode_wav_to_bns() with codec = 0, and dynamically parsing channel_data_offset and channel_dsp_offset from the pointer table in decode_bns_to_wav(). Added regression tests in tests/test_platforms_wii_banner.py.
  • NDSBanner & extract_rom — DSi Extended Animated Icon offset misalignment, missing crc_dsi calculation/validation, zero-dependency PNG export/injection, and ROM banner truncation (miorom.platforms.nds.banner, miorom.platforms.nds.rom):
  • DSi Animated Icon Offset Misalignment & Missing Padding: In the Nintendo DS/DSi cartridge banner container specification (Version 0x0103), the DSi animated icon payload (0x1180 = 4480 bytes) is located at offset 0x1240..0x23BF (total container size 0x23C0 / 9152 bytes), preceded by 0x800 bytes (2048 bytes) of reserved zero-filled padding at 0x0A40..0x123F. NDSBanner.to_bytes() previously appended dsi_extended_data immediately at 0x0A40 without the 0x800 bytes of zeros, shifting the animation payload 2048 bytes too early (yielding 0x1BC0 bytes) and breaking DSi firmware compatibility. In from_bytes(), reading authentic 0x23C0 DSi banners sliced from 0x0A40:, capturing 6528 bytes (including the 0x800 zero padding) into dsi_extended_data. Fixed by padding to offset 0x1240 and 0x23C0 in to_bytes(), and cleanly extracting 0x1240:0x23C0 (4480 bytes) in from_bytes() while maintaining backward compatibility for legacy/unpadded buffers.
  • Missing crc_dsi Checksum Calculation & Validation: header.crc_dsi (covering 0x1240..0x23BF, 0x1180 bytes) was previously ignored during to_bytes(recalc_crc=True) (remaining 0) and is_crc_valid() never verified crc_dsi. Fixed by computing RetroChecksum.crc16_modbus across the 0x1180 DSi payload during serialization and validating it in is_crc_valid() for Version 0x0103 banners.
  • Zero-Dependency PNG Export & Injection: Added to_png_bytes() and save_icon_png(path) to NDSBanner using the pure-Python standard-library PNGCodec, and enhanced set_image() to accept PNG binary bytes, PNGImage, and PNG file paths without requiring Pillow.
  • ROM Extraction Banner Truncation: extract_rom in miorom.platforms.nds.rom previously extracted a hardcoded 0x840 bytes for banner.bin, truncating v2 (0x940), v3 (0xA40), and DSi (0x23C0) banners. Fixed by inspecting the banner version to extract the full container size. Added regression tests in tests/test_platforms_nds_banner.py.
  • BRSTMFile — HEAD Part 1 0x34-byte sizing, missing final_block_padded_size, and relative audio offset desynchronization (miorom.platforms.wii.brstm):
  • 0x34-Byte HEAD Part 1 Structure & Missing final_block_padded_size: BRSTMHeadPart1Struct was previously defined as 0x30 bytes (48 bytes) instead of the standard 0x34 bytes (52 bytes) specified by the Nintendo Wii SDK and format specifications. The 4-byte final_block_padded_size field at offset 0x28 was omitted, causing samples_per_seek (0x2C) and seek_entries_per_block (0x30) to shift backwards into offset 0x28 and 0x2C, and truncating Part 1 by 4 bytes. In to_bytes(), this shifted Part 2 (p2_off) and Part 3 (p3_off) offsets by 4 bytes (e.g. p2_off = 0x44 instead of 0x48), producing non-standard BRSTM files that failed on hardware and third-party tools. In from_bytes(), authentic BRSTMs suffered field corruption and could not read final_block_padded_size.
  • Absolute vs Relative audio_data_offset: In to_bytes(), audio_data_offset was hardcoded to 0x20 instead of the absolute file offset data_offset + 0x20. On Wii hardware and decoders like vgmstream that rely on audio_data_offset as an absolute seek target from file start, this caused DMA to stream from offset 0x20 (the RSTM header), resulting in static noise or crashes. In from_bytes(), reading authentic BRSTMs where audio_data_offset >= data_offset mistakenly added data_off + p1.audio_data_offset, doubling the offset and skipping past the audio stream. Fixed by adding final_block_padded_size at offset 0x28, aligning Part 1/2/3 offsets to 0x34 bytes, computing absolute audio_data_offset = data_offset + 0x20 in to_bytes(), and handling both absolute and relative audio offsets in from_bytes(). Added regression tests in tests/test_platforms_wii_brstm.py.
  • MSBFFile — UTF-8 encoding corruption on Big-Endian archives & ChoiceNode options crosstalk (miorom.text.msbf):
  • UTF-8 on Big-Endian Corruption: In to_bytes(), the codec ternary operator "utf-16-be" if used_endian == ">" else "utf-16-le" if enc_code == 1 else "utf-8" evaluated used_endian == ">" first, forcing "utf-16-be" for all Big-Endian archives (Nintendo Wii/GameCube standard) even when encoding == "utf-8", while appending a single-byte 0x00 null terminator. In from_bytes(), the same operator precedence bug forced "utf-16-be" decode while scanning for single-byte 0x00, causing the parser to match the high zero byte of the first character, cutting options to empty strings "" or corrupting byte alignment.
  • ChoiceNode Options Crosstalk: In to_bytes(), FLW3NodeStruct.param1 was hardcoded to 0 instead of recording the option offset len(all_options), and in from_bytes(), every ChoiceNode sliced option_strings[:branch_count] starting at index 0, causing all choice nodes across a dialogue tree to inherit the options of the very first choice node. Fixed by correcting the codec selection to ("utf-16-be" if endian == ">" else "utf-16-le") if encoding == "utf-16" else "utf-8", scanning 2-byte aligned \x00\x00 terminators in UTF-16, and properly indexing/tracking option offsets across ChoiceNodes. Added regression tests in tests/test_text_msbf.py.
  • BRSARSoundArchive.to_bytes — sparse & non-contiguous file_id table index desynchronization (miorom.platforms.wii.brsar): BRSARSoundArchive.to_bytes() previously compacted the INFO File Table by iterating only over sorted(self._files.keys()) and emitting len(self._files) entries, while writing the raw original s.file_id into the Sound Table without index remapping. In authentic Wii BRSAR archives where sounds reference non-contiguous or sparse file IDs (e.g. following RBNK/RSEQ entries), s.file_id exceeded the truncated File Table length, causing re-parsed sound entries to receive zero sizes/offsets and shifting file storage keys in self._files, leading to immediate ValueError: No file payload found errors during export_wav(). Fixed by structuring the File Table to accommodate all file IDs up to max_f_id with 1:1 index-to-file_id correspondence, emitting empty (0, 0) descriptors for unreferenced slots, and safely ignoring zero-length entries during re-parsing. Added regression test in tests/test_platforms_wii_brsar.py.
  • PointerRelinker._measure_slot & find_free_space — unbounded scan, adjacent string corruption & protected range collision (miorom.text.pointer_relinker): PointerRelinker._measure_slot() previously scanned forward until hitting fill_byte without bounding against the next pointer target or null delimiters (0x00). In tightly-packed string tables (where no fill byte separates consecutive strings) or in ROMs without fill_byte padding, _measure_slot() scanned through adjacent strings and data all the way to the end of the ROM. When relinking with a shortened string, the calculated tail padded with fill_byte across the unbounded range, wiping out subsequent strings, pointer targets, and ROM memory. Furthermore, find_free_space() did not avoid active pointer tables or previously allocated free-space blocks, allowing relocated strings to clobber table structures. Fixed by: (1) adding max_bound to _measure_slot() constrained by subsequent pointer targets and allocated boundaries; (2) terminating slot measurement on 0x00 (when fill_byte != 0); and (3) adding exclude_ranges to find_free_space() to ensure free space runs never overlap pointer table entries or active allocations. Added regression tests in tests/test_text_pointer_relinker.py.
  • BranchRelocator.patch_single_branch & rebase_block — MIPS little-endian & architecture support (miorom.asm.reloc_calc) (BUG-59): Fixed hardcoded Big-Endian >I packing and unpacking in MIPS branch patching which corrupted branch opcodes and displacements on Little-Endian MIPS platforms (Sony PlayStation 1 / R3000A and Sony PSP / Allegrex). Added endian parameter ("little", "big", "<", ">") defaulting to Little-Endian for psx, psp, mips_le, and mips, and Big-Endian for n64 and mips_be. Furthermore, implemented complete MIPS branch scanning and rebasing in BranchRelocator.rebase_block() supporting standard conditional branches (BEQ, BNE, BLEZ, BGTZ), REGIMM branches (BLTZ, BGEZ, BLTZAL, BGEZAL), and Branch Likely instructions (BEQL, BNEL, BLEZL, BGTZL).
  • pack_both_u16 & pack_both_u32 — both-endian field byte-swapping (miorom.platforms.iso.builder): Fixed a standard-compliance bug in ISO 9660 (ECMA-119 Section 7.2.3 & 7.3.3) both-byte order serialization where pack_both_u16() and pack_both_u32() used schema.pack("<HH", val, val) and schema.pack("<II", val, val). Because <HH and <II format strings apply little-endian encoding to both arguments, the big-endian half of all Primary Volume Descriptor fields (volume_space_size, logical_block_size, volume_sequence_number, path_table_size) and all Directory Record fields (lba, size, volume_sequence_number) was byte-swapped into little-endian. Big-endian readers and standards validators reading big-endian fields saw completely corrupted sector numbers (e.g., LBA 20 decoded as sector 335,544,320; block size 2048 decoded as 8). Corrected to serialize with <H + >H and <I + >I, ensuring bit-exact ECMA-119 both-byte order compliance.
  • MSBFFile — big-endian struct header & node deserialization/serialization desync (miorom.text.msbf): MSBFFile.from_bytes() and .to_bytes() previously invoked MSBFHeaderStruct, MSBFSectionHeaderStruct, FLW3HeaderStruct, and FLW3NodeStruct .from_bytes() and .to_bytes() without passing endian=endian / endian=used_endian. Because BinaryStruct defaults to Little-Endian ("<"), authentic Big-Endian Nintendo Wii MSBF files (b"\xFE\xFF") had section sizes decoded as byte-swapped integers (e.g. 24 bytes became 402 MB), skipping all subsequent sections (FEN1, FOP1), while node descriptors had node_type byte-swapped (type 1 became 256), breaking node classification. In .to_bytes(), serializing with endian=">" produced corrupt hybrid binaries with little-endian struct headers and big-endian payloads. Fixed by explicitly threading endianness through all BinaryStruct from_bytes/to_bytes invocations across MSBFFile.
  • OverlayRegion & MemoryOverlayMapper — linear address translation performed on compressed overlays (miorom.core.overlay_mapper): OverlayRegion.ram_to_rom() and rom_to_ram() previously ignored the overlay flags field (bit 24 indicating LZ10/BLZ compression status) and blindly applied linear delta arithmetic, returning meaningless addresses for compressed overlays whose physical ROM bytes do not correlate 1:1 to RAM. Added compression validation returning None by default (with optional raise_on_compressed=True raising CompressedOverlayError) and documenting that overlays must be decompressed prior to mapping offsets in decompressed memory.
  • MemoryOverlayMapper.detect_dma_copies — empty stub and lack of GBA DMA3 transfer detection (miorom.core.overlay_mapper): The heuristic scanner for GBA DMA3 transfers previously matched the literal register address 0x040000D4 but was an empty stub (pass), always returning an empty list. Implemented complete ARM and Thumb heuristic scanning using UniversalDisassembler to disassemble candidate code windows, perform register state tracking and traceback to literal pool LDRs / immediates, correlate sequential stores to 0x040000D4 (DMA3SAD), 0x040000D8 (DMA3DAD), and 0x040000DC (DMA3CNT), and construct valid DMACopyRecord instances with accurate pc_address, source_address, destination_address, and word_count (lower 16-bit count from DMA3CNT).
  • interleave_smd — non-16KB chunk zero-padding (miorom.platforms.md.rom) (BUG-89): Non-16KB-aligned Sega Genesis ROMs are now zero-padded to the next 16KB boundary before interleaving into Super Magic Drive format to prevent payload corruption.
  • RomIntegrityManager.fix — preservation of SMD container format (miorom.core.integrity) (BUG-91): Auto-fixing Mega Drive ROM checksums now correctly checks if the input was SMD format and re-interleaves back into SMD before returning.
  • MSBTFile.from_bytes — invalid string offset alignment preservation (miorom.text.msbt) (BUG-92): Invalid out-of-bounds string offsets now fallback to empty strings instead of desynchronizing the label-to-message mapping.
  • MSBTFile — control tag null bytes string truncation (miorom.text.msbt) (BUG-93): Control tags (0x000E) containing null bytes in their argument payloads now properly advance the parser position without truncating the message text.
  • GBASavePatcher.patch_to_sram — 7-byte FLASH_V string replacement (miorom.save.gba_save) (BUG-94): Replaced 7-byte b"FLASH_V" with 7-byte b"SRAM_V\x00" instead of 6-byte b"SRAM_V", avoiding trailing phantom characters in patched ROM headers.
  • PlanarTileCodec — SNES Mode 7 linear chunky encoding (miorom.graphics.planar) (BUG-95): Mapped Mode 7 tiles to linear chunky 8bpp format rather than bitplane planar formats.
  • MDECVideoDecoder — 0xFE00 EOB token draining & chroma quantization (miorom.graphics.mdec) (BUG-96, BUG-97): Correctly drains trailing 0xFE00 EOB marker on coefficient overflow and added support for custom chroma quantization tables.
  • PPFPatcher.apply_stream — truncated target image rejection (miorom.patch.ppf) (BUG-98): Rejects target images smaller than the required validation block offset when non-dummy validation blocks are present.
  • BMFont.to_bitmap_font — unicode code point bounds check (miorom.text.bmfont) (BUG-99): Wrapped character conversion with validation to prevent unhandled ValueError when glyph IDs exceed 0x10FFFF.
  • BMGFile.from_bytes — string pool offset bounds checking (miorom.text.bmg) (BUG-100): Out-of-bounds DAT1 message offsets are safely caught and assigned empty strings.
  • BRRCodec.encode_block — shift selector middle range threshold (miorom.audio.brr) (BUG-101): Lowered shift threshold from max_res > 7 to max_res > 3 to eliminate clipping in small-amplitude audio samples.
  • SDATContainer.to_bytes — empty SYMB block INFO offset & FAT header count (miorom.audio.sdat) (BUG-102, BUG-103): Fixed INFO block offset to start at 64 when SYMB is absent, and ensured the FAT block header count reflects the total entry count.
  • SappyCodec.parse_sample — fixed-point 10-bit pitch conversion (miorom.audio.sappy) (BUG-104): Corrected sample rate calculation to divide fixed-point pitch by 1024 (pitch >> 10).
  • VAGCodec.encode — SPU-ADPCM shift filter direction (miorom.audio.vag) (BUG-105): Inverted shift search order to select optimal scaling factors starting from smallest scale (12) down to 0.
  • XAStreamDecoder.decode_sector — continuous filter state across sound units (miorom.audio.xa) (BUG-106): Sequential sound units now preserve filter prediction history across sound units within CD-XA sectors.
  • Elf32RelaStruct — signed relocation addend packing (miorom.link.elf) (BUG-107): Packed r_addend as signed 32-bit integer (I32) to properly support negative relocation addends.
  • PatternScanner.iter_file — overlapping chunk duplicate prevention (miorom.scanner.pattern) (BUG-108): Tracks maximum yielded offset to prevent duplicate matches across sliding buffer windows.
  • DeepScanner.scan — trailing partial block entropy averaging & NARC BOM endianness (miorom.scanner.deep) (BUG-109, BUG-110): Counts exact processed blocks in Shannon entropy calculation and properly resolves BOM endianness (0xFEFF vs 0xFFFE).
  • DolBinary.__init__ — immutable bytes input conversion (miorom.link.dol) (BUG-111): Wraps binary input in bytearray() to prevent TypeError when modifying sections in-place.
  • CascadingArchive.unpack — bounded entry count validation (miorom.archive.cascading) (BUG-112): Validates declared entry count against payload length before allocating memory buffers.
  • LZ10.compress & LZ11.compress — sliding window rfind upper bound allows forward self-reference (miorom.compression.lz10, miorom.compression.lz11): The lookahead match search previously used search_pos + 3 as the rfind upper boundary, allowing matches starting ahead of search_pos to produce zero or negative displacement backreferences, causing decompressors to fail with CompressionError: invalid displacement. Fixed boundary to search_pos + 1 and constrained lookahead extension to pos + match_len < in_pos.
  • APLib._BitWriter.put_gamma — zero bit emission on unit value (miorom.compression.aplib): Variable-length gamma coding for val <= 1 executed 0 loop iterations in while temp > 1, emitting 0 bits and corrupting all downstream bitstreams. Added explicit base-case check emitting a single 0 terminator bit for val <= 1. Also expanded maximum sliding window lookback from 32,000 to standard 32,767 (0x7FFF) bytes.
  • RLE.compress — off-by-one run length cap & unhandled end marker (miorom.compression.rle): Corrected maximum compressed run-length condition to run_len < (0x7F + 3) (max 130 bytes). Previously, run_len < (0x7F + 4) allowed runs up to 131 bytes, where run_len - 3 = 128 = 0x80 overflowed the 7-bit length field and zeroed it via 0x80 | 0x80, causing decompression to truncate data. Additionally implemented include_end_marker behavior to emit END_OF_STREAM (0xFF) when requested. Added regression tests for 130, 131, and 262 bytes and randomized roundtrip fuzzing in tests/test_rle.py.
  • BinaryLifter.lift — conditional branch CFG fall-through edge omission (miorom.script.lifter): BinaryLifter ignored is_conditional=True on conditional branch instructions (beq, bne, bc, bgt, etc.), emitting only a single successor edge to the taken branch target and omitting the fall-through edge. This caused fall-through blocks to become orphan nodes with empty predecessors (predecessors: []), broke CFG topology, distorted cyclomatic complexity calculations ($E - V + 2$) in BinDiffEngine, and broke decompiler control flow. Fixed by emitting both branch-taken and fall-through successor edges for IROp.BRANCH_COND instructions, maintaining bidirectional predecessor/successor links, and properly connecting non-branch/non-return block terminations to sequential fall-through leaders.
  • IROp.CMP & Comparison Instruction Lowering (miorom.script.ir, miorom.script.lifter): Comparison instructions (cmp, cmn, tst, teq, cmpw, cmpwi, cmplw, cmplwi, cp, etc.) across ARM/Thumb, PowerPC, M68K, 6502/65816, and SM83 were previously dropped or lowered to NOP(), losing operands. Added IROp.CMP opcode to IROp, preserved comparison operands, and synthesized relational condition expressions (e.g. r0 == 0) for subsequent conditional branches and C decompilation (if (r0 == 0) goto ...;).
  • _disasm_arm — ARM conditional branch mnemonic condition suffixes (miorom.asm.disasm): ARM 32-bit branch instructions with condition codes (cond < 14) emitted bare "b" / "bl" mnemonics despite is_conditional=True. Added condition code suffix formatting (beq, bne, ble, bgt, blt, etc.) matching standard ARM disassembly and ensuring lifter pattern matching correctly distinguishes conditional branches from unconditional jumps.
  • BinaryLifter.convert_to_ssa — condition expressions wrapped into IRVar (miorom.script.lifter): convert_to_ssa matched register names using a.startswith("r"), incorrectly converting expression strings like "r0 == 0" into IRVar(name="r0 == 0", version=0). Refined register recognition with _is_reg to accurately identify register tokens (r0..r31, $a0, d0..d7, a, x, y, sp, lr, pc) without mangling condition expressions.
  • StructProfiler.profile_struct — field boundary and width heuristic desynchronization (miorom.core.struct_profiler): Fixed a major heuristic flaw where 4-byte fields were only accepted as UINT32 when max_val > 0xFFFF. Small 32-bit values (such as IDs or counters in 0..0xFFFF) previously fell through to 2-byte analysis without advancing col += 4, permanently desynchronizing all subsequent field offsets. Replaced the simplistic max_val > 0xFFFF check with 4-byte natural alignment checks (stride % 4 != 0 or col % 4 == 0) and independent half-word variance/delta analysis (is_split_16), accurately distinguishing true 32-bit fields from adjacent 16-bit fields (e.g. <IHHI). Added comprehensive regression tests in tests/test_struct_profiler.py.
  • StructProfiler.profile_struct — API composability and StrideCandidate auto-unwrapping (miorom.core.struct_profiler): profile_struct() now accepts Union[int, StrideCandidate, List[StrideCandidate]], allowing direct pipelining of autodetect_stride() candidate results into profile_struct(data, candidates) without raising TypeError. Empty lists, invalid types, and non-positive strides now raise clear, descriptive ValueError and TypeError exceptions.
  • Huffman.compress & Huffman.decompress — empty input (b"") roundtrip support (miorom.compression.huffman): Compressing empty data previously returned a truncated 6-byte stream (28 00 00 00 00 00) missing the required 2-byte tree table placeholder ((tree_size_code + 1) * 2 = 2), causing decompress() to fail with Malformed Huffman tree: extends beyond input data. Updated compress() to emit a valid 7-byte header with a 2-byte tree placeholder (b"\x00\x00"), and updated decompress() to early-return b"" when uncompressed_size == 0. Added regression tests in tests/test_huffman.py.
  • calc_mips_jump & _disasm_mips — 256MB segment validation and delay-slot base address (miorom.asm.branch, miorom.asm.disasm): Added optional src_addr parameter to calc_mips_jump() with upper 4-bit segment checking against the delay slot PC (src_addr + 4), preventing invalid cross-segment jumps. Fixed MIPS J/JAL target address reconstruction in UniversalDisassembler to calculate upper bits from (address + 4) & 0xF0000000.
  • BranchRelocator.rebase_block & TrampolineHook — out-of-range ARM branch protection & 6502 16-bit address guard (miorom.asm.reloc_calc, miorom.asm.trampoline): ARM branch rebasing in rebase_block() now checks item.in_range before writing rebased instruction bytes to avoid corrupting out-of-range branches. Added explicit bounds checking to TrampolineHook.create_6502_hook() to ensure cave_ram_addr <= 0xFFFF before packing 16-bit JMP targets. Documented architectural limitations regarding PC-relative displaced instructions in ARM, Thumb, and PowerPC trampolines.
  • NDSRom.replace_file & get_banner_title — FAT table shift tracking & UTF-16-LE alignment (miorom.platforms.nds.rom): Replacing ROM files with larger data buffers shifted the underlying bytearray but left self.fat_offset and downstream FAT record pointers pointing to pre-shift locations, corrupting FAT tables. Updated replace_file() to adjust self.fat_offset when the FAT resides after the replaced region. Fixed banner title string truncation to align down to even boundaries (null_pos - (null_pos % 2)) to avoid corrupting UTF-16-LE character pairs.
  • NARCFile._parse_btnf_names & PSXSaveHeaderStruct — flat-file BTNF name parsing & save title field bounds (miorom.platforms.nds.narc, miorom.platforms.psx.memory_card): Implemented flat-file BTNF name table parsing in NARCFile so extracted files retain real filenames instead of generic numbers. Partitioned PSXSaveHeaderStruct title field into 32-byte Shift-JIS title and 32-byte reserved padding, preventing garbage characters in parsed save titles.
  • VFSFile.truncate & IPS32 Truncation Guard (miorom.archive.vfs, miorom.patch.ips): Implemented missing VFSFile.truncate(size=0) method to prevent AttributeError crashes when opening existing virtual files in write mode. In IpsPatcher.apply(), guarded trailing IPS32 truncation sizes with 0 < trunc_size < len(result) to prevent trailing archive padding from wiping patched buffers.
  • SSEQSequence & SSEQTrack — MIDI transpiler delta-times, loop preservation & tempo endianness (miorom.audio.sseq): Fixed opcode 0xE0 tempo decoding from big-endian to little-endian (<H). Implemented two-pass loop compilation in _compile_track_events() so loop_start and loop_end markers emit compliant 0x94 jumps instead of being silently dropped. Added individual 0-tick VLQ delta times between consecutive RPN CC messages (CC101, CC100, CC6, CC38) for pitch bend range in MIDI output. Replaced fragile dummy-event RPN detection in from_midi() with persistent track-level state variables.
  • ScriptVM & BytecodeStreamSplicer — signed integer argument emission & relative branch counts (miorom.script.vm, miorom.script.splicer): Assembler Pass 2 now handles s8, s16, and s32 argument types without silently dropping operands. In BytecodeStreamSplicer.splice_and_shift(), restricted operand repacking and adjusted_count increments to branches that actually cross the splice boundary.
  • PointerRelinker & StringTablePipeline — banked pointer table scanning & signed relative pointer extraction (miorom.text.pointer_relinker, miorom.text.pipeline): Added bank_table: Optional[List[int]] parameter to PointerRelinker.scan_pointer_table() to properly map per-entry bank numbers in banked pointer tables. Added two's-complement sign extension to raw_ptr in StringTablePipeline.extract_strings() when is_relative=True, restoring extraction of backward-relative script pointers.
  • KosinskiCodec.compress — descriptor high-byte truncation (miorom.compression.kosinski): Flushing the final descriptor word omitted writing out[desc_pos + 1] = (val >> 8) & 0xFF. For inputs where remaining descriptor bits exceeded 8 bits, bits 8..15 were zeroed, causing the decompressor to misinterpret literal bytes as invalid backreferences and fail with CompressionError. Fixed by writing both descriptor bytes during finalization.
  • BpsPatcher.parse — null-byte dropping and linear overhead in expanded ROMs (miorom.patch.bps): Converting BPS patches into concrete PatchHunk records filtered expanded ROM bytes using if byte:, silently omitting all 0x00 bytes in target expansion areas. Replaced byte-by-byte iteration with unified slice hunk emission (PatchHunk(source_len, output[source_len:])), preserving all data bytes and accelerating parsing from $O(N)$ to $O(1)$.
  • BranchRelocator — missing second-halfword validation for Thumb BL (miorom.asm.reloc_calc): Re-basing Thumb branch instructions unpacked the second halfword into raw_val2 but never verified the prefix/suffix match (raw_val2 & 0xF800) == 0xF800. Added validation so non-BL 32-bit Thumb instructions starting with 0xF000 are not corrupted and correctly raise ValueError.
  • PowerPCBranch.encode_b — out-of-range absolute branch silent truncation (miorom.asm.branch): Boundary checks for PowerPC branch encoding explicitly bypassed absolute branches (if not absolute). Because PowerPC BA/BLA instructions only provide a 26-bit effective address range, passing physical memory addresses (e.g. GameCube/Wii RAM 0x80000000) caused silent truncation via & 0x00FFFFFF to 0x00000000. Added explicit validation ensuring absolute targets fit within the 26-bit signed addressing window.
  • resolve_mips_jump — pipeline delay slot address mismatch (miorom.asm.branch): Standalone MIPS jump target resolution calculated upper segment bits using src_addr & 0xF0000000 instead of (src_addr + 4) & 0xF0000000 (the delay slot PC). Updated to align with MIPSBranch.decode_j() and the official MIPS architecture specification.
  • apply_outline_1px — missing image buffer size validation (miorom.graphics.pixel_math): Added upfront validation ensuring len(rgba_bytes) == width * height * 4, preventing unhandled IndexError exceptions on truncated or malformed image buffers.
  • Codebase Hardening & Linter Compliance (miorom.*): Standardized and hardened variable names and binary spec unpacks across the entire codebase for strict Ruff and Flake8 compliance. Resolved ambiguous variable names (l -> match_len, length, run_len), documented intentionally unconsumed binary format spec fields with leading underscore prefixes (_bom, _version, _file_size, _curr_tile, _min_hook_size), deduplicated namespace exports in __init__.py, and resolved circular import dependencies between core.integrity and text.pipeline without altering any binary parsing behaviors.
  • Namespace Cleanliness & Export Deduplication (miorom.__init__, miorom.errors): Resolved duplicate and out-of-order exports in __init__.py (XRefAnalyzer, XRefGraph, XRefType, AssetProtocol, ArchiveProtocol, EmulatorClientProtocol), moved late module imports prior to __all__, and removed duplicate Optional typing import in errors.py.
  • cmd_disasm — UniversalDisassembler calls undefined disassemble_stream (miorom.cli.main): Executing the miorom disasm CLI command on non-m68k architectures crashed with an AttributeError because it called disassemble_stream. Updated to call the correct disassemble descriptor method with base_address and max_instructions arguments.
  • cmd_scan_text — TranslationRow instantiated with unexpected length argument (miorom.cli.main): Exporting scanned dialogue strings to a CSV file (miorom scan-text --output) threw a TypeError due to an unexpected length keyword argument being passed to TranslationRow. Removed the invalid argument.
  • ROM Handler — GBRom property typo cartridge_type_name (miorom.rom.handlers.cartridge): Processing Game Boy ROMs through the universal ROM handler triggered an AttributeError because it attempted to access .cartridge_type_name instead of the actual .cartridge_type property. Fixed the property reference.
  • Missing Module Exports (miorom.__init__): The __all__ export list declared several symbols (NESRom, NESHeaderStruct, PBPFile, SFOFile, NARCEntry, XRefDirection) that were never imported into the namespace, causing undefined-all-variable linting errors. Added the missing imports from their respective platform modules.
  • Missing NCLRFile Typing Import (miorom.platforms.nds.ncer): Added missing NCLRFile import to fix F821 undefined name linting errors during type hint evaluation.
  • LZExtendedSizeStruct & RLEExtendedSizeStruct — missing definitions and imports for extended sizes (miorom.compression.lz11, miorom.compression.rle): Decompressing large LZ11 or RLE streams (> 0xFFFFFF bytes or 0-size fallback) triggered fatal NameError exceptions due to missing struct definitions and imports. Added missing imports and defined RLEExtendedSizeStruct locally in rle.py to restore extended size support.
  • ExtractArchiveStep & PackArchiveStep — mismatched U8Archive classmethod signatures (miorom.pipeline.engine): The pipeline engine incorrectly called non-existent U8Archive.from_bytes() and from_directory() methods, crashing during U8 archive tasks. Fixed by using the correct U8Archive.extract_all() and U8Archive.pack() API methods.
  • LiteralRelocator — scan_all method missing from UniversalInstructionScanner (miorom.asm.literal_relocator): The universal fallback path for literal relocation called an undefined scan_all method. Replaced with the correct find_code_pointers method and updated the data argument to code.
  • table_detector — unreachable code and undefined variables (miorom.scanner.table_detector): The extract_strings method contained a leftover, unreachable return extracted statement after a valid list return. Cleaned up the dead code.
  • Missing Domain Exceptions and Typing Imports (miorom.*): Standard domain exceptions (ParseError, RelocationError) and typing hints (List, Tuple, Any, Sequence) were omitted from imports in several modules (e.g. expander.py, tilesheet.py, vm.py, vwf.py, heap.py, ncgr.py, sfo.py, u8.py), masking underlying parsing errors as fatal NameError exceptions. Injected all missing top-level module imports to restore correct error propagation and type resolution.
  • AutoRelocationManager — relative pointer arithmetic inverted (miorom.patch.relocator): read_pointer_value and write_pointer_value previously calculated relative targets using base_address + raw_val and new_target_offset - base_address. Fixed to compute relative targets from the pointer's own buffer location (pointer_offset + raw_val and new_target_offset - pointer_offset) with signed delta support.
  • PointerRelinker.relink — free-space search colliding with pointer tables (miorom.text.pointer_relinker): In ROMs filled with the designated fill byte (e.g. 0xFF), find_free_space() could allocate memory inside the pointer table range, corrupting subsequent pointers during batch relinking. Added protected_ranges support to guarantee free-space runs never overlap pointer tables or active allocations.
  • DialogueDissector.extract_from_table — target offset calculation ignores pointer_type (miorom.script.dialogue_dissector): Hardcoded subtraction of base_address broke relative and banked pointer tables. Added pointer_type and bank parameters to extract_from_table and delegated offset resolution to PointerRelinker._to_rom_offset().
  • DialogueDissector.inject_translations — relocation pointer rewrite uses absolute arithmetic (miorom.script.dialogue_dissector): Relocated string pointers were rewritten using allocated_off + block.base_address regardless of addressing mode. Now delegates to relinker._to_pointer_value(allocated_off, pointer_type, bank) to properly format relative and banked pointer updates.
  • SmartScriptRepacker.repack_with_translations — label drift on unknown multi-byte opcodes (miorom.script.repacker): Unregistered instructions with raw multi-byte payloads were sized as single-byte instructions during Pass 1, skewing subsequent branch label offsets. Sizing and emission now account for raw_bytes payloads.
  • SmartAutoPaginator.paginate_text — manual newlines collapsed inside paragraphs (miorom.text.paginator): Splitting text with whitespace-collapsing .split() stripped explicit \n linebreaks chosen by translators. Updated to process paragraph lines independently and preserve hard line breaks.
  • find_pane — NUL-padded pane name matching failure (miorom.platforms.wii.brlyt): Comparing 16-byte fixed-string pane names directly failed when entries contained trailing NUL padding. Now strips trailing \x00 characters from both search target and candidate names.
  • apply_vertical_gradient — missing buffer validation (miorom.graphics.pixel_math): Added buffer length validation ensuring mask_bytes matches expected 8-bit mask or 32-bit RGBA dimensions, and documented source alpha channel modulation behavior.
  • VWFHookEngine.synthesize_width_routine — Thumb routine jumps into 32-bit literal pool (miorom.asm.vwf_hook_engine): In 16-bit Thumb mode, B .done branched directly to offset 0x10 where table_vaddr was stored as raw 32-bit data, causing the processor to execute address bytes as instructions. Updated routine layout to branch cleanly over the 4-byte literal pool (B .after_pool) to offset 0x18, loading via PC-relative LDR r2, [PC, #12] with proper 4-byte boundary alignment.
  • DynamicVWFInjector.generate_arm_vwf_hook & generate_mips_vwf_hook — premature return inside trampoline caves (miorom.text.vwf_injector): Embedding generated ARM and MIPS lookup routines into TrampolineHook's inline cave payload caused premature subroutine returns (BX lr / jr $ra; nop) before displaced instructions and trampoline return branches could execute. Added standalone: bool = True parameter (defaulting False in VWFHookEngine.synthesize_width_routine) to generate inline fall-through variants that branch over literal pools directly to the cave tail.
  • ThumbBranch.encode_b — missing 11-bit branch range check (miorom.asm.branch): Unconditional 16-bit Thumb B instructions have an 11-bit signed halfword displacement ([-2048, 2046] bytes). Target offsets exceeding this limit were silently masked via & 0x7FF, generating branches to corrupted addresses. Added range check raising RelocationError on overflow, along with decode_b() and decode_bl() decoding helpers.
  • Yaz0.compress — lookback window unbounded by MAX_DIST (miorom.compression.yaz0): When search_depth exceeded MAX_DIST (4096 bytes), start_window lookback calculation allowed match distances greater than 4096, which overflowed the 12-bit distance field and corrupted compressed output. Capped start_window to src_pos - min(src_pos, min(search_depth, MAX_DIST)).
  • DteCodec.encode — unmapped Unicode characters silently truncated (miorom.text.dte): Non-ASCII Unicode characters unmapped by the provided charmap or without a charmap were truncated to 8 bits via ord(ch) & 0xFF, silently altering characters into unrelated ASCII codes. Now raises ValueError when ord(ch) > 0xFF.
  • JapaneseCharMapMiner.search_kana_word — 2-byte search skips unaligned ROM offsets (miorom.text.japanese_charmap): In 2-byte search mode, candidate positions stepped by 2 bytes, missing 16-bit Japanese character sequences located at odd byte offsets (e.g. following 1-byte command opcodes). Updated search to advance by 1 byte.
  • TagSyntaxValidator.validate & TagManager.decode_tags — bracket ordering ignored and escaped newlines conflated (miorom.text.tags): TagSyntaxValidator.validate relied on simple bracket counts, incorrectly validating reversed (]...[) and nested ([[...]]) brackets; replaced with a bracket stack validator. TagManager.decode_tags previously replaced literal \\n with <ENTER>, destroying escaped strings; made escaped newline conversion optional via convert_escaped_newlines: bool = False.
  • AFSArchive — hardcoded ASCII encoding corrupts Japanese TOC filenames (miorom.archive.afs): TOC filenames in CRI AFS archives were strictly decoded and encoded using ASCII, replacing Shift-JIS / CP932 characters common in Dreamcast, Saturn, and PS2 titles with replacement characters. Added encoding: str = "cp932" support to __init__, from_bytes(), and to_bytes() with automatic fallback.

[1.0.3] — 2026-09-14

Fixed

  • ScriptVMDissector.splice_and_relink — branch operand offset off by base_offset (miorom.script.script_dissector): All branch operand write positions were computed as absolute ROM offsets, but new_buf is a 0-indexed local script slice. Every branch in a script not at ROM offset 0 was written to the wrong position. Fixed: all op_pos values now subtract script.base_offset before indexing into new_buf.
  • ScriptVMDissector.splice_and_relink — return value is a script slice, not a full ROM buffer (miorom.script.script_dissector): Callers assigning the return value directly to rom[:] would silently truncate the ROM. Docstring now explicitly documents the splice contract and the correct pattern: rom[base_offset : base_offset + len(new_script)] = new_script.
  • ScriptVMDissector.splice_and_relink — length-prefix overflow raises generic error (miorom.script.script_dissector): For has_length_prefix=True instructions, translations longer than 255 encoded bytes raised a bare ValueError from bytearray.append() with no indication of which instruction caused it. Now raises an explicit ValueError with the instruction index, ROM offset, and byte count.
  • StringTablePipeline.extract_strings — is_relative=True pointer arithmetic inverted (miorom.text.pipeline): The formula base_address + raw_ptr was used for relative pointers, which is incorrect. A relative pointer is an offset from the pointer's own ROM location; the correct formula is ptr_location + raw_ptr. The previous behavior produced garbage file offsets whenever base_address != 0, causing silent extraction of wrong bytes and corrupted .po output.
  • StringTablePipeline.inject_from_po — RomIntegrityManager.fix() size mismatch silently left checksum partially uncovered (miorom.text.pipeline): When the integrity manager returned a buffer shorter than the post-relocation ROM, the unconditional buffer[:len(fixed_data)] = fixed_data slice left the relocation tail with stale checksum coverage. Emits a RuntimeWarning when sizes differ; uses buffer[:] = fixed_data for the matching-size path.
  • NestedArchiveVFS class methods read / write / list — writes silently discarded (miorom.core.vfs): All three class methods instantiated an empty NestedArchiveVFS() with no root path. write() mutations were never saved to disk. Methods now infer the root archive path from the first URI segment and load/save through it. write() accepts an optional output_path parameter.
  • NestedArchiveVFS — unsupported container format error does not name supported formats (miorom.core.vfs): ParseError on unrecognized headers (e.g. RARC, AFS) now explicitly states which formats are supported (U8, Yaz0-compressed U8) instead of reporting a generic parse failure.
  • extract_or_quantize_palette — all-transparent image collapses to single palette entry (miorom.platforms.wii.tpl): Images where all unique pixel colors have alpha < 128 (e.g. particle effects) produced a palette of only (0,0,0,0), losing all pixel data silently. The function now preserves distinct semi-transparent colors as separate palette entries when no opaque colors are present.

[1.0.2] — 2026-09-14

Added

  • Nintendo GX Paletted Texture Codecs & TPL TLUT Serialization (miorom.platforms.wii.tpl):
  • Full decoding and encoding support for CI4 (Format 8, 4bpp, 8x8 tiles) and CI8 (Format 9, 8bpp, 8x4 tiles).
  • TPLPaletteHeaderStruct: Binary struct representing standard Nintendo GX 12-byte palette descriptor.
  • decode_gx_palette() & encode_gx_palette(): Pure Python 16-bit big-endian palette codecs for IA8 (0), RGB565 (1), and RGB5A3 (2).
  • extract_or_quantize_palette(): Adaptive RGBA color palette extraction providing lossless 100% exact colors when unique count <= 16 (CI4) or <= 256 (CI8), with frequency-based clustering fallback.
  • TPLFile.to_bytes(): Full binary serialization for paletted textures with strict 32-byte hardware DMA sector alignment and offset backpatching.
  • TPLImage.summary() and TPLImage.to_terminal_ascii(): Terminal visual inspection and summary metadata.
  • U8Archive.pack_dict() & U8Archive.extract_dict(): In-memory packing and unpacking of U8 archives without touching physical disk.
  • Forensic Texture Inspector & Visual Regression Engine (miorom.graphics.texture_inspector):
  • TextureInspector.inspect(): Deep forensic analysis of texture containers (TPL, BTI, PNG), validating dimensions, tile strides, format bit depths, palette capacity vs unique colors, and payload truncation warnings.
  • TextureInspector.diff(): Mathematical visual regression comparator computing exact modified pixel count, percentage, bounding box $(x_{\min}, y_{\min}, x_{\max}, y_{\max})$, max channel delta, and multi-level alpha preservation checks.
  • TextureInspector.render_ascii(): Console ASCII art rendering of texture images.
  • CLI commands: miorom gfx inspect <file> [--json], miorom gfx ascii <file> [-w 40], miorom gfx diff <orig> <mod> [--json].
  • Glyph Harvester & Bitmap Font Recomposer (miorom.graphics.glyph_bank):
  • Glyph: Bitmap character representation preserving exact pixel channels, dimensions, and advance metrics.
  • GlyphBank.harvest_boxes(): Harvests glyphs via explicit bounding box coordinates.
  • GlyphBank.harvest_widths(): Harvests horizontal glyph strips with variable character widths.
  • GlyphBank.harvest_grid(): Harvests glyphs from regular grids with optional blank column trimming.
  • GlyphBank.auto_dissect(): Automatically segments glyph blocks from horizontal text strips by analyzing empty column intervals.
  • GlyphBank.recompose() & recompose_rgba(): Reassembles target translated phrases using source glyphs with source-over alpha blending, kerning/tracking, and 1-px border overlap without destroying adjacent bevel highlights.
  • CLI command: miorom gfx recompose <source_image> -t <target> -o <out.png>.
  • Declarative Script VM Dissector & Relinking Engine (miorom.script.script_dissector):
  • VMInstructionDef: Added schema parameter supporting declarative SchemaField instances (I16, U16, U32, or BinaryStruct subclasses) to eliminate brittle struct format strings.
  • ScriptVMDissector.disassemble(): Decodes instruction streams dynamically via schema.unpack(), resolving relative branches (BranchType.RELATIVE), absolute jumps (BranchType.ABSOLUTE), and switch jump tables.
  • ScriptVMDissector.splice_and_relink(): Automatically calculates bytecode deltas, adjusts instruction offsets, recalculates relative/absolute jump operands, and repacks updated operands via schema.pack(), preventing corruptions during arbitrary text expansion.
  • End-to-End Dialogue Localization Pipeline (miorom.text.pipeline):
  • TextDialoguePipeline: Orchestrates dialogue table scanning, control code tokenization (ControlCodeTokenizer), variable-width font line wrapping (AdaptiveLineWrapper), and pointer table relocation (PointerRelinker).
  • JapaneseCharmap (miorom.text.japanese_charmap): Full Shift-JIS and EUC-JP multi-byte character mapping with bidirectional dakuten/handakuten decomposition and composition.
  • Architecture Consolidation & Legacy Shim Removal:
  • miorom.text.line_wrapper: Consolidated typography and word wrapping; unified WordWrapper, FontMetrics, and PixelWordWrapper with VwfLineWrapper.
  • miorom.text.dte: Consolidated Dual-Tile Encoding; unified DTEToken and DTEMiner with DteOptimizer and DteCodec.
  • miorom.text.tags: Consolidated markup control tags; unified TagSyntaxValidator and TagValidationReport with TagManager.
  • miorom.asm.branch: Consolidated low-level branch arithmetic; unified calc_arm_branch, resolve_arm_branch, calc_thumb_branch, resolve_thumb_branch, calc_mips_jump, resolve_mips_jump, calc_mips_branch, resolve_mips_branch, calc_6502_branch, and resolve_6502_branch directly into branch.py.
  • miorom.asm.xref: Consolidated cross-reference analysis; unified XRefDatabase, XRefRecord, XRefDirection, and SymbolicXrefEngine with GlobalXrefEngine.
  • Purged 6 redundant legacy modules (wrapper.py, pixel_wrapper.py, dte_miner.py, tag_validator.py, branch_calc.py, xref_engine.py) and migrated all imports directly to canonical modules across the entire codebase and test suites.
  • Pure Binary Primitives & Schema Dogfooding:
  • Replaced standard library struct dependencies in dialogue pipelines, script VM relinkers, and user workflows with pure primitives: BinaryReader, BinaryWriter, and BinaryStruct.
  • Nested Container Virtual File System (miorom.core.vfs):
  • NestedArchiveVFS: Unified URI addressing for deeply nested container hierarchies (outer.arc::inner.arc::path/to/file.ext).
  • In-memory traversal and extraction for U8 (.arc), Yaz0 (.szs), RARC, and AFS archives with zero temporary disk files.
  • Atomic outward repacking (write_uri()): Injects modified assets into innermost containers and automatically rebuilds all enclosing parent archives outward, maintaining 32-byte alignment and transparent compression.
  • CLI commands: miorom vfs list <uri>, miorom vfs read <uri> -o <out>, miorom vfs write <uri> -i <in>.

Fixed

  • nds.nftr — NFTR Builder Conformance Rewrite (miorom.platforms.nds.nftr):
  • NFTRFont.to_bytes() now serializes Nitro SDK conforming NFTR binaries, resolving game engine freezes caused by non-conforming structures.
  • FINF block (28-byte FNIF): Header correctly serializes a 28-byte FNIF block including pre-calculated pointer fields pGlyph, pWidth, and pMap required by NNS_G2dFontInit (ARM9 0x02058E54). Legacy implementation generated a 16-byte stub with garbage pointers, causing Data Aborts during New Game / save selection screens.
  • CMAP multi-block chaining: CMAP blocks (PAMC) are now segmented by run type and linked via next_block_offset. Contiguous runs of ≥4 codes produce 24-byte Type 0 (Direct) blocks; isolated glyphs or sparse runs produce Type 2 (Scan list) blocks. Eliminates monolithic 64KB Type 1 tables (0x0000–0xFFFF) that caused runtime parsing timeouts.
  • PLGC (CGLP) block: baseline, max_width, and bpp fields are accurately serialized from corresponding NFTRFont attributes instead of zeroes.
  • HDWC (CWDH) block: Per-glyph width triplets (left_margin, width, advance) are fully preserved during both read and write operations. The legacy implementation always wrote (0, cell_width, advance), corrupting proportional typography.
  • Bitwise pixel codec: Legacy string-based bitstream codec replaced with fast bitwise helpers _encode_glyph_pixels() / _decode_glyph_pixels() supporting 1, 2, 4, and 8 bpp tile formats.
  • NFTRFont.from_bytes(): Reads all three CWDH fields (left_margin, width, advance) as per-glyph triplets; parses baseline and max_width from PLGC; extracts cell_height from FINF.
  • NFTRGlyph: Added left_margin attribute storing per-glyph horizontal bearing, preserved across serialization round-trips.
  • Round-trip fidelity: from_bytes() -> to_bytes() generates bit-for-bit identical output against retail NDS NFTR fonts (font12x12.NFTR, ed_font12x12.NFTR). Verified with pytest tests/test_nftr.py (2 passed).

[1.0.1] — 2026-11-11

Fixed

  • Update documentation reference links.

[1.0.0] — 2026-11-11

Added

  • Menu Tilemap & Nametable Dissector (miorom.graphics.tilemap_dissector):
  • TilemapTextRun: Structured record of a contiguous graphic text tile sequence; carries row/col origin, direction, decoded text, palette bank, flip flags, and available padding count. Supports round-trip to_dict() / from_dict() for JSON layout files.
  • TilemapMenuBox: Rectangular window descriptor enclosing a list of TilemapTextRun items; bounding box coordinates are relative to the parent tilemap.
  • TilemapDissector.scan_text_runs(): Scans a Tilemap horizontally or vertically for contiguous runs of CharMap-mapped tiles; computes trailing blank padding for every run and filters runs shorter than min_length.
  • TilemapDissector.export_text_grid(): Renders a 2D ASCII/Unicode visual grid of all decoded text characters in a tilemap for layout proofreading and screenshot comparison.
  • TilemapDissector.splice_label(): Injects a translated string into the tilemap at an arbitrary (row, col) position with left, center, or right alignment, clears residual tiles to pad_tile, and enforces max_width tile boundary safety.
  • TilemapDissector.extract_menu_box(): Extracts all horizontal text runs from a rectangular sub-region and remaps coordinates back into the parent tilemap coordinate space.
  • TilemapDissector.export_layout_json() / import_layout_json(): Full-roundtrip JSON serialization and deserialization of discovered TilemapTextRun lists for external editing and translation pipeline integration.
  • TilemapDissector.apply_layout_dict(): Batch translation applicator: accepts a list of {"row", "col", "new_text", "max_width", "align"} dicts and splices each label into the tilemap in a single pass.
  • TilemapDissector.clear_region(): Fills an arbitrary rectangular region with a blank tile index, suitable for erasing original Japanese graphic text before writing a replacement.
  • Assembly Trampoline & VWF Hook Engine (miorom.asm.vwf_hook_engine):
  • VWFHookConfig: Declarative configuration dataclass specifying architecture (arm, thumb, mips, snes, 6502), hook ROM/RAM addresses, original instruction bytes for integrity verification, glyph width table source, optional explicit cave address, fallback width, character range, and endianness.
  • VWFDeploymentReport: Comprehensive deployment audit trail: hook record, table ROM/RAM offset and size, lookup routine ROM/RAM offset and size, total code cave bytes consumed, and verification status. Provides to_dict() for pipeline logging.
  • VWFHookEngine.verify_hook_site(): Byte-exact integrity guard that compares actual ROM bytes at hook_rom_offset against original_instr_bytes before any modification; raises RelocationError on mismatch to prevent double-patching.
  • VWFHookEngine.synthesize_width_routine(): Generates architecture-specific machine code for glyph width table lookups — ARM32 (r0=char, r1+=width), Thumb-16 (8-byte halfword routine with fallback), MIPS32 ($a0=char, $a1+=width with lui/lb), SNES W65C816 (PHP/REP/LDA.L table,X/PLB/PLP/RTL), and MOS 6502 (SEC/SBC/TAX/LDA table,X).
  • VWFHookEngine.deploy(): End-to-end orchestration — validates hook site, serializes width table into code cave, synthesizes lookup routine, generates multi-architecture trampoline hook (payload + displaced instructions + return branch), and atomically patches rom_buffer. Supports simulate=True for dry-run preflight checks without modifying the buffer.
  • Japanese Gojūon Relative Search & Auto-.TBL Generator (miorom.text.japanese_charmap):
  • JapaneseWordMatch: Single kanji/kana word match record with byte offset, decoded string, charmap key bytes, confidence score, and character class flags.
  • JapaneseMiningCluster: Grouped cluster of consecutive Japanese word matches sharing gojūon row membership, density, and overall confidence score.
  • JapaneseCharMapMiner: Relative search engine discovering game-specific Japanese character encodings by mining hiragana/katakana gojūon rows with dakuten variants; generates draft .tbl files via auto_generate_tbl().
  • Font Graphic Bank Scanner & VWF Width Table Hunter (miorom.graphics.font_dissector):
  • FontGeometry: Glyph bounding box record (left, top, right, bottom, advance_width) computed from 1bpp/2bpp/4bpp pixel data.
  • DissectedGlyph: Individual dissected glyph with tile index, raw pixel bytes, geometry, and is-blank flag.
  • WidthTableCandidate: Discovered width table candidate inside a ROM buffer with offset, inferred byte-width per entry, and measured advance width histogram.
  • FontCandidate: Font bank candidate with ROM offset, glyph count, tile format, bit-depth, glyph list, and optional associated width table.
  • FontDissector.scan_font_banks(): Heuristic font bank discovery combining entropy scoring, stroke fill density filtering, and glyph diversity checks to reject noise regions.
  • FontDissector.scan_width_tables(): Discovers VWF width tables adjacent to font banks by matching advance width distributions.
  • FontDissector.export_sheet_png(): Renders dissected glyphs to a PNG spritesheet using PNGCodec (pure Python, zero external dependencies).
  • FontDissector.inject_glyphs() / inject_width_table(): Round-trip glyph pixel and width table injection back into ROM buffers.
  • Retro Text Compression Scanner & Huffman/DTE Tree Hunter (miorom.compression.text_compression_hunter):
  • HuffmanNodeEntry: Individual node in an array-based binary Huffman tree (left child index, right child index, symbol value, and is-leaf flag).
  • HuffmanTreeCandidate: Discovered Huffman tree in a ROM buffer with offset, node count, depth, symbol set, and decoded bitstream payload.
  • DteDictionaryCandidate: Discovered DTE bigram table with offset, entry count, active bigrams (non-dummy pairs), byte coverage, and uniqueness ratio.
  • TextCompressionHunter.scan_huffman_trees(): Dynamic Huffman tree discovery via root-0 traversal without hardcoded node-count assumptions; rejects cyclic graphs and non-contiguous node sets.
  • TextCompressionHunter.parse_huffman_tree_auto(): Parses array-based Huffman trees with automatic size detection for arbitrary retro Huffman variants (19, 23, 45, 63 nodes, etc.).
  • TextCompressionHunter.decompress_huffman(): Bitstream decompressor producing decoded byte payloads for content verification.
  • TextCompressionHunter.scan_dte_tables(): Scans for DTE bigram tables with alignment, dummy-pair, and uniqueness filtering.
  • TextCompressionHunter.compress_huffman(): Adaptive optimal Huffman compressor that re-encodes translated text and serializes the complete node array plus bitstream.
  • Event Script Bytecode Disassembler & Branch Relinker (miorom.script.script_dissector):
  • VMOpcodeType: Enumeration of instruction categories: TEXT, BRANCH_REL, BRANCH_ABS, SWITCH, CONTROL, and TERMINATOR.
  • VMInstructionDef: Opcode schema carrying opcode byte, name, type, fixed length, text terminator, length-prefix flag, and operand format string.
  • DissectedInstruction: Fully decoded script instruction with index, ROM offset, byte length, opcode, raw bytes, text payload (if any), branch target, relative delta, switch targets, and operand offset.
  • DissectedScriptVM: Disassembled script container with base offset, instruction list, and total byte count; provides get_dialogues() for filtered dialogue extraction and to_po() for direct GNU gettext PO catalog export via PoHandler.
  • ScriptVMDissector.disassemble(): Linear sweep disassembler accepting arbitrary opcode tables; correctly handles all five instruction categories including null-terminated and length-prefixed dialogue strings.
  • ScriptVMDissector.splice_and_relink(): Dynamic text replacement engine — injects translated strings at precise byte positions, computes cumulative shift maps for all replacement sites, and automatically recalculates relative branch deltas, absolute jump targets, and switch/jump table entries across the entire script buffer.

  • Cookbook & Golden End-to-End Integration Pipelines (docs/COOKBOOK.md, tests/test_golden_pipelines.py):

  • Production-ready, copy-pasteable recipes and integration tests covering the complete ROM hacking lifecycle:

    • Text Translation, Far Relinking, Checksum Recalculation & Multi-Format Patching (TextStreamScanner, PointerRelinker, RetroChecksum, BpsPatcher, IpsPatcher, UpsPatcher).
    • Code Cave Discovery, Subroutine Assembly, Branch Rebasing & Inline Trampoline Hooking (CodeCaveManager, HookManager, ArmHookBuilder, BranchRelocator, UniversalDisassembler).
    • Planar Graphics Extraction, Tile Deduplication, Nametable Synthesis & Hardware OAM Layout (PlanarTileCodec, TileDeduplicator, HardwareOamCodec, SpriteDescriptor).
    • Cross-Version Binary Diffing & Function Matching (BinDiffEngine, UniversalDisassembler).
    • Anti-Piracy Detection & Surgical NOP Bypass (AntiPiracyBypasser).
  • ARM/Thumb Inline Hook Builder & Code Cave Manager (miorom.asm.hook_manager):

  • CodeCave: dataclass representing a free-space region with start, size, used, free, and end properties.
  • HookRecord: dataclass capturing installed hook metadata: hook_offset, cave_offset, arch, hook_bytes, original_bytes, and hook_size.
  • CodeCaveManager: scans ROM buffers for contiguous fill-byte runs (scan()), manually registers known free regions (register()), allocates cave space (allocate()), and writes code into caves with bookkeeping (write_to_cave()).
  • ArmHookBuilder: static encoder for ARM BL, ARM B, Thumb BL (long-range two-halfword form), and Thumb B (11-bit short-range) branch instructions, plus build_arm_trampoline_return() for epilogue generation.
  • HookManager: high-level hook installer for both ARM (install_arm_hook()) and Thumb (install_thumb_hook()) modes; writes hook branch and cave trampoline (cave code + return branch) directly into a bytearray buffer and records all installed hooks.
  • Sega Genesis / Mega Drive 68000 Disassembler (miorom.asm.m68k):
  • M68kInstruction: dataclass with offset, bytes_, mnemonic, operands, size, is_branch, is_call, is_return, and branch_target; exposes .text property for formatted output.
  • M68kDisassembler: single-pass big-endian 68000 decoder supporting NOP, ILLEGAL, RTS/RTR/RTE, TRAP, BRA/BSR/Bcc (8-bit, 16-bit, and 32-bit displacement), MOVEQ, MOVE.b/w/l, MOVEA, JSR, JMP, LEA, ADD/ADDA, SUB/SUBA, CMP/CMPA, CMPI, ADDI, SUBI, AND, OR, EOR; full effective-address mode decoding (Dn, An, (An), (An)+, -(An), d16(An), d8(An,Xn), abs.w, abs.l, PC-relative, immediate); DC.W fallback for unrecognised opcodes.
  • disassemble() method for sequential multi-instruction decoding with count and end bounds.
  • format_listing() produces annotated hex+mnemonic text output compatible with standard 68k assembler listing format.
  • PC-Relative Branch Rebasing & Relocation (miorom.asm.reloc_calc):
  • BranchRelocation: dataclass capturing instruction offset, architecture, mnemonic, old/new PC addresses, destination address, byte representation, and displacement range validation.
  • BranchRelocator: low-level PC-relative branch displacement rebasing engine supporting MOS 6502, W65C816, Z80, SM83, M68K, ARM, Thumb, and MIPS; includes patch_single_branch() and rebase_block() with internal/external target discrimination and dictionary-based destination remapping.
  • Multi-Byte Japanese Binary Text Stream Scanner (miorom.scanner.text_stream):
  • TextStreamSpan: dataclass recording detected text boundaries, encoding, confidence metrics, character count, kanji/kana density, and terminator presence.
  • TextStreamScanner: binary scanner detecting unmapped dialogue and message blocks encoded in Shift-JIS, EUC-JP, UTF-16LE, UTF-16BE, and ASCII; applies strict lead-byte/trail-byte FSM validation to filter machine code and false positives, and includes scan_string_table() for sequential dialogue cluster extraction.
  • Sub-Byte Bitfield & Packed Record Codec (miorom.core.bitfield):
  • BitField and BitFieldSchema: declarative schema for arbitrary bit-width fields (1 to 64 bits), signed two's complement interpretation, and configurable MSB-first or LSB-first bit order.
  • BitFieldCodec: encoder and decoder supporting unpack(), unpack_from(), unpack_all(), pack(), pack_into() for in-place buffer mutation, and pack_all() for tabular record arrays.
  • Tile Deduplicator & VRAM Optimizer (miorom.graphics.tile_dedup):
  • DeduplicatedTileEntry: mapping entry linking original tile positions to optimized unique indices with H-flip and V-flip metadata; includes nametable word converters for SNES, Genesis, GBC, and GBA.
  • TileDedupResult: optimization report capturing unique tile pixel buffers, entries, and compression metrics.
  • TileDeduplicator: detects identical 8x8 tiles and symmetrical flipped copies (horizontal, vertical, HV-flip); includes deduplicate_raw_bpp() for direct binary planar tile buffer optimization.
  • Hardware Sprite OAM Descriptor Codec (miorom.graphics.oam):
  • SpriteDescriptor: normalized representation of a 2D hardware sprite object with coordinates, tile ID, palette, flipping, priority, and dimension attributes.
  • HardwareOamCodec: multi-platform hardware sprite attribute codec supporting NES/Famicom 4-byte OAM, Game Boy / GBC 4-byte OAM, SNES dual split tables (Table 1 + Hi-OAM Table 2), Sega Genesis / Mega Drive 8-byte SAT, and GBA 8-byte OAM.
  • CLI Commands Expansion (miorom.cli.main):
  • scan-text: multi-byte binary text stream scanner with threshold and encoding filtering.
  • disasm: multi-architecture disassembler supporting m68k, arm, thumb, mips, ppc, 6502, 65816, z80, and sm83.
  • checksum: retro cartridge and file checksum calculation (CRC32, MD5, SHA1, GB, SNES, N64, Genesis).
  • reloc-branch: batch branch displacement rebasing for moved code blocks.
  • tile-dedup: binary planar tile deduplication with H/V flipping and nametable generation.
  • ARM Thumb-16 Expansion (miorom.asm, miorom.script):
  • Comprehensive Thumb 16-bit instruction disassembler formats 1–19 (UniversalDisassembler._disasm_thumb) for GBA and NDS ARM7/ARM9.
  • SSA Intermediate Representation lifter for Thumb instructions (BinaryLifter._lift_thumb) enabling C pseudocode decompilation.
  • Function prologue detection for Thumb 16-bit push patterns (0xB5xx).
  • Fluent ThumbSnippet micro-assembly builder (AsmSnippet.thumb()) for GBA & NDS code-caves.
  • MOS 6502 & W65C816 Architectures (miorom.asm, miorom.script):
  • Complete 256-opcode table with dynamic REP/SEP status bit tracking across sequential disassembly for 8-bit and 16-bit Accumulator/Index register switching.
  • SSA IR lifter and C decompilation for 6502 (NES) and 65816 (SNES).
  • SNES function prologue detection (php; rep; pha, phb; phd).
  • Fluent SnesSnippet builder (AsmSnippet.snes()) for SNES routines.
  • PowerPC 32-bit Code-Cave Tools (miorom.asm.snippet, miorom.asm.disasm):
  • Fluent PpcSnippet builder (AsmSnippet.ppc()) for GameCube and Wii (stwu, lwz, stw, addi, li, lis, ori, mr, mflr, mtlr, b, bl, blr).
  • Added stwu, lwzu, and or/mr instruction decoding to UniversalDisassembler._disasm_ppc.
  • Nintendo Entertainment System Platform (miorom.platforms.nes):
  • NESRom and NESHeaderStruct supporting iNES and modern NES 2.0 cartridge formats.
  • Automatic mapper detection (NROM, MMC1, MMC3, MMC5, UNROM, CNROM, etc.).
  • PRG ROM and CHR ROM/RAM separation, modification, and 512-byte trainer buffer extraction.
  • Universal Patching System (miorom.patch.ups):
  • Native pure-Python UpsPatcher implementing UPS format with Variable-Length Quantity (VLQ) encoding, XOR diffing, and CRC32 verification.
  • Nintendo DS 2D Graphics Suite (miorom.platforms.nds):
  • NCLRFile: Nitro Color Palette (.nclr) reader and serializer for BGR555 color palettes.
  • NCGRFile: Nitro Character Graphic (.ncgr) reader and serializer for 4bpp/8bpp tile graphics.
  • NSCRFile: Nitro Screen Resource (.nscr) background tilemap screen layout reader and serializer.
  • Console Audio Codecs & Exporters (miorom.audio):
  • Sony PS1 & PS2 VAG SPU-ADPCM (VAGHeader, VAGCodec, VAGFile): 16-byte block parser, encoder, decoder, and direct WAV exporter.
  • Super Nintendo SPC700 BRR (BRRCodec): 9-byte BRR block encoder and decoder with S-DSP 4-filter Gaussian interpolation, loop tagging, and WAV generation.
  • Nintendo GameCube & Wii DSP-ADPCM (DSPADPCMCodec): 8-byte frame audio decoder and WAV converter.
  • Compression Codecs (miorom.compression):
  • Nintendo Yay0 (Yay0): 3-stream LZSS decompressor and compressor used in N64 and early GameCube titles (Super Mario 64, Zelda OoT, Star Fox 64).
  • aPLib (APLib): Pure-Python high-ratio LZ decompressor and compressor supporting raw streams and AP32 container headers.
  • Registered yay0 and aplib into unified compress() and decompress() dispatchers.
  • Symbolic Cross-References Engine (miorom.asm.xref_engine):
  • SymbolicXrefEngine and XRefDatabase: Multi-architecture cross-reference analysis across ARM, Thumb, PowerPC, MIPS, W65C816, MOS 6502, and data pointer tables.
  • Call graph construction and IDA / Ghidra style ; CODE XREF: and ; DATA XREF: disassembly annotations.
  • C Struct Overlay Mapper (miorom.core.cstruct):
  • CStructOverlay and CStructInstance: Compiles ANSI C struct syntax into binary layouts with dot/dict access, table iterations (read_table()), and in-place binary writes (write()).
  • AngelCode BMFont Suite & Minimal PNG Codec (miorom.text.bmfont):
  • BMFont: Two-way parser and generator for AngelCode .fnt files (both Text and XML formats).
  • Automatic 2D texture atlas shelf packing for BitmapFont glyphs.
  • PNGCodec: Built-in pure-Python minimal PNG encoder and decoder (grayscale & RGBA) using only standard library zlib.
  • Sega Genesis 4bpp & SNES Mode 7 Planar 8bpp Tile Formats (miorom.graphics.tiles):
  • Added high_nibble_first support for Sega Mega Drive / Genesis 4bpp chunky tiles and planar 8bpp for SNES Mode 3/4/7 in decode_tile, encode_tile, decode_tileset, and encode_tileset with preset platform aliases (genesis, md, snes8, mode7).
  • GBA Sappy / M4A Sound Engine Scanner & Sample Ripper (miorom.audio.sappy):
  • Added SappyScanner and SappyCodec for discovering Sappy song tables, voice tables, instrument envelopes, and extracting DirectSound 8-bit signed PCM samples with direct WAV conversion.
  • ISO 9660 Disc Tree Synthesizer & Rebuilder (miorom.platforms.iso.builder):
  • Added pure-Python Iso9660Builder for creating standards-compliant ISO 9660 optical disc images from in-memory buffers or filesystem folders with PVD, Path Tables, Directory Records, and custom system areas.
  • Sega Genesis VDP 9-bit RGB333 Palette & Color Tables (miorom.graphics.palette):
  • Discrete 9-bit RGB333 DAC conversion (to_md_color(), from_md_color()) with Genesis standard CRAM packing format 0000_BBB0_GGG0_RRR0.
  • Genesis CRAM binary byte stream import and export (to_md_bytes(), from_md_bytes()).
  • Adobe Color Table (.act) 768-byte / 772-byte binary palette import and export (to_act(), from_act()).
  • JASC-PAL Paint Shop Pro text format palette import and export (to_jasc_pal(), from_jasc_pal()).
  • NES 6502 & SNES 65816 Code-Cave Trampoline Hooks (miorom.asm.trampoline):
  • Direct create_6502_hook() synthesizing 3-byte absolute JMP $xxxx hooks with automatic displaced instruction execution and return jump.
  • 24-bit direct create_snes_hook() synthesizing 4-byte 24-bit far hooks using either JML (far jump) or JSL (far subroutine call returning via RTL).
  • Integrated into unified TrampolineHook.create_hook() and binary cave finder TrampolineHook.auto_hook().
  • Compressed ISO (CSO / CISO) Image Suite (miorom.platforms.iso.cso):
  • CSOImage: Sector-based random-access reader supporting raw Deflate/zlib decompression, LBA reading (read_sector, read_sectors), cross-sector byte slicing (read_bytes), and disk streaming (decompress_to_file).
  • Pure-Python compress_iso() engine converting raw disc images into standards-compliant CSO archives with sector-level compression decision (deflate vs raw storage) and bit 31 index tagging.
  • Sony PlayStation Portable & PS1 Classics Container Suite (miorom.platforms.psp):
  • SFOFile: Two-way parser and binary serializer for Sony PARAM.SFO metadata objects supporting UTF-8 string, ASCII string, and uint32 types.
  • PBPFile: Container unpacker and builder for Sony EBOOT.PBP archives handling all 8 canonical sections (PARAM.SFO, ICON0.PNG, ICON1.PMF, PIC0.PNG, PIC1.PNG, SND0.AT3, DATA.PSP, DATA.PSAR), with multi-file extraction (extract_all()) and binary synthesis (to_bytes(), save()).
  • PlayStation Patch Format (PPF v1, v2, v3) Engine (miorom.patch.ppf):
  • Pure-Python PPFPatcher supporting PPF 1.0, PPF 2.0, and PPF 3.0 standards with 512-byte headers, undo-data chunks, and sector 0x9320 validation block checks.
  • Streaming constant-memory disc patch applier (apply_stream()), in-memory patcher (apply()), and patch creator (create()).
  • Integrated into universal patch routing (create_patch(), apply_patch()) with automatic .ppf extension and magic byte auto-detection.
  • EA RefPack (QFS) & Okumura LZSS Compression Codecs (miorom.compression):
  • RefPack: Electronic Arts RefPack / QFS decompressor and compressor supporting 2-byte, 3-byte, and 4-byte match opcodes, literal run blocks, and 3-byte size headers.
  • LZSS: Standard Haruhiko Okumura (1989) 4096-byte sliding window LZSS decompressor and compressor with configurable initial fill bytes and positions.
  • Registered refpack and lzss into unified compress() and decompress() dispatchers.
  • Sega Saturn & Dreamcast Disc Bootstrap Suite (miorom.platforms.sega_disc):
  • SaturnDiscHeader: Sector 0 security bootstrap header parser and serializer for Sega Saturn CD-ROMs with region unlocking (make_region_free()).
  • DreamcastIpBin: Sector 0 (32KB) IP.BIN bootstrap header parser and CRC16 recalculator (calculate_crc()) for Dreamcast GD-ROMs and MIL-CDs with region-free unlocking.
  • GDISheet, GDITrack: Parser and formatter for Dreamcast GD-ROM multi-track descriptor sheets (disc.gdi) with automatic high-density track discovery.
  • GBA BIOS SWI Symbolic Resolver & Multiboot Builder (miorom.platforms.gba):
  • GBASwiResolver: Full 32-entry lookup database for official Game Boy Advance BIOS software interrupts with automatic disassembly annotation for Thumb (0xDFxx) and ARM (0xEFxxxxxx) instructions, and binary call-site scanner (scan_swi_calls()).
  • GBAMultiboot: 256KB EWRAM Multiboot (.mb) payload builder and validator with ARM entry branch, Nintendo logo insertion, complement checksum calculation, and parameter blocks.
  • Font Glyph Injector & Latin Extender Suite (miorom.graphics.font_injector):
  • FontGlyphInjector: Comprehensive glyph injection and extraction engine supporting 1bpp, 2bpp planar (Game Boy, NES), and 4bpp (SNES, Genesis, GBA) tiles for localizing Japanese games.
  • Built-in LATIN_8X8_BITMAPS dataset containing all 95 printable ASCII characters plus extended Latin accented letters (é, è, ê, á, à, í, ó, ú, ñ, ç, ¿, ¡).
  • Proportional Variable-Width Font (VWF) binary width table generator (generate_vwf_table()) and character table mapping exporter (generate_tbl_mapping()).
  • VWF Pixel-Accurate Line Wrapper & Dialogue Paginator (miorom.text.line_wrapper):
  • VwfLineWrapper: Pixel-accurate word-wrapper accepting width sources of multiple forms: width dict, raw VWF binary table bytes, arbitrary callable, or FontGlyphInjector / FontMetrics instances.
  • Control code preservation mode: bracketed and braced game tags ([wait], {hero}, <color:red>) are tokenized and excluded from pixel measurements while being kept intact in output.
  • Emergency long-word splitting with optional hyphenation (hyphenate_overflow=True).
  • paginate(): Automatic multi-page dialogue layout with configurable line count per page and page-break tokens.
  • analyze(): Returns LineWrapResult with structured TextBoxPage objects, per-line pixel widths, and overflow diagnostics for batch translation validation.
  • GameCube & Wii DOL Executable Parser, Translator & Section Injector (miorom.platforms.gc.dol):
  • DolFile: Full header parser and binary serializer for GameCube/Wii DOL executables with up to 7 text and 11 data sections.
  • Bidirectional RAM virtual address ↔ DOL file offset translation (address_to_offset(), offset_to_address()).
  • Direct RAM memory read/write interface (read_memory(), write_memory()) with section boundary safety checks.
  • add_section(): Injects additional text or data sections into the first available header slot with configurable alignment, enabling new ASM code caves and expanded translation data regions.
  • allocate_code_cave(): High-level helper that zeroes and registers a new code block section, returning the RAM address and file offset ready for instruction injection.
  • DTE / MTE Dictionary Optimizer & Compressor (miorom.text.dte):
  • DteOptimizer.analyze_ngrams(): Counts n-gram frequencies (configurable min_len–max_len) across a text corpus while preserving bracketed control codes.
  • DteOptimizer.build_dictionary(): Synthesizes the optimal DTE/MTE substitution dictionary (default 2–4 character pairs) ranked by net byte savings (len-1)×frequency, with reserved_codes exclusion and start_code assignment control; returns DteStats with compression ratio metrics.
  • DteOptimizer.to_tbl() / from_tbl(): Round-trip serialization of DTE token tables to and from standard romhacking .tbl character mapping format, preserving trailing spaces within token values.
  • DteCodec.encode() / decode(): High-performance two-way greedy longest-match encoder and decoder integrated with CharMap for full round-trip text translation pipelines.
  • Translation Memory & Pure-Python Fuzzy String Matcher (miorom.text.translation_memory):
  • TranslationMemory: In-memory translation memory engine implementing pure-Python dynamic programming Levenshtein distance matching without external dependencies.
  • lookup(): Multi-candidate retrieval returning structured TmLookupResult and TmMatch with exact (100%) and fuzzy match scoring.
  • pre_fill_po(): Automatically populates untranslated GNU gettext .po entries with TM matches and tags fuzzy translations for QA.
  • JSON serialization (export_json(), from_json()) for portable translation memory persistence.
  • Universal Script Extractor & Reinserter (miorom.script.extractor):
  • ScriptExtractor: Two-way script extraction and reinsertion pipeline integrated with CharMap and custom control code schemas.
  • Export formats: Human-readable block format (to_txt(), from_txt()) and tabular localization format (to_csv()).
  • Safe binary injection (insert()): Writes translated bytecode directly into ROM buffers with dry-run verification mode and slot overflow detection.
  • Script Table Pointer Relinker & Free-Space Relocator (miorom.text.pointer_relinker):
  • PointerRelinker: Multi-architecture pointer table scanner and relinker supporting 1, 2, 3, and 4-byte pointers with big-endian and little-endian byte orders.
  • Dynamic free space allocation: In-place string rewrites with fill-byte padding when expanded text fits, and automatic relocation to ROM caves when strings expand.
  • Detailed RelinkReport logging entries relinked, relocated, bytes saved, and mapped free space regions.
  • Game Boy / GBC ROM Builder & MBC Mapper Suite (miorom.platforms.gb.builder):
  • GBHeader: Cartridge header parser and serializer covering titles, MBC cartridge types (MBC1, MBC2, MBC3, MBC5), CGB/SGB flags, and destination codes.
  • Checksum recalculators: Pure bitwise calculate_header_checksum() and 16-bit calculate_global_checksum().
  • GBRomBuilder: Assembles multi-bank Game Boy ROMs, manages 16KB switchable banks, inserts official Nintendo boot logos, and provides power-of-two ROM capacity expansion (expand()).
  • ARM & Thumb Inline Hook Builder & Code Cave Manager (miorom.asm.hook_manager):
  • CodeCaveManager: Automated scanner for unused ROM padding blocks (0x00 or 0xFF) and tracking registry for code cave allocation.
  • ArmHookBuilder: Pure bitwise branch instruction synthesizers for ARM BL/B, Thumb BL (long-range 4-byte halfwords), Thumb B (short-range), and return trampolines.
  • HookManager: High-level hook installer writing call branches at hook offsets and payload trampolines into allocated code caves.
  • Sega Genesis / Mega Drive 68000 Disassembler (miorom.asm.m68k):
  • M68kDisassembler: Motorola 68000 instruction decoder covering data movement (MOVE, MOVEA, MOVEQ), ALU (ADD, SUB, CMP, AND, OR, EOR), branching (BRA, BSR, Bcc), control (JMP, JSR, RTS, TRAP, NOP), and addressing modes (Dn, An, (An), (An)+, -(An), d16(An), #imm, abs.w, abs.l).
  • Structured output (M68kInstruction) with branch target resolution, call classification, and formatted assembly listings (format_listing()).

  • Nintendo DS ARM9 & ARM7 Overlay Table Manager & Relocator (miorom.platforms.nds.overlay):

  • NDSOverlayTable: Two-way binary serializer for ARM9 (y9.bin) and ARM7 (y7.bin) overlay definition tables; supports add, update, and RAM address relocation per overlay ID.
  • NDSOverlayCompressor: LZ10 compression and decompression for overlays with automatic compressed flag management.
  • NDSOverlayManager: High-level orchestrator for extracting, replacing, and relocating ARM9/ARM7 overlay payloads within NDSRom images; auto-expands ROM buffer when translated overlays exceed original capacity.
  • OverlayAllocationReport: Structured result recording old/new RAM size, file size, and compression state per operation.
  • SNES SPC700 Sound File Parser & BRR Sample Dumper (miorom.audio.spc):
  • SpcFile: Full .spc format reader and serializer including 64KB SPC700 RAM, 128-byte S-DSP register block, IPL ROM, and ID666 metadata (song title, game, dumper, artist, date, duration).
  • list_samples(): Parses the S-DSP BRR directory table (DSP 0x3D) to enumerate all instrument/SFX sample entries with start and loop addresses.
  • extract_brr_sample(): Walks BRR 9-byte blocks from sample start until end-flag, yielding raw BRR binary.
  • dump_samples_to_wav(): Converts all discovered BRR samples to standard RIFF/WAVE 16-bit PCM files using BRRCodec.
  • Direct RAM read/write access (read_ram(), write_ram()) for audio data modification.
  • Universal Game Genie & GameShark Cheat Code Engine & ROM Hard-Patcher (miorom.patch.cheats):
  • NesGameGenie: NES 6-character (no compare) and 8-character (with compare) decoder and encoder using the standard Game Genie cipher.
  • SnesGameGenie: SNES 8-character decoder and encoder (base-16 DF4709156BC8A23E alphabet) producing hyphenated XXXX-XXXX format.
  • GenesisGameGenie: Sega Genesis / Mega Drive 8-character 40-bit cipher decoder for 24-bit address and 16-bit word patches.
  • GameBoyGameGenie: Game Boy 6-character and 9-character (with compare) decoder and encoder.
  • GameShark: GameShark / Action Replay XXXXXXXX YYYY format decoder for GBA, N64, and PS1.
  • parse_cheat_code(): Format auto-detector resolving cheat strings to CheatCode records without requiring explicit system parameter.
  • hard_patch_rom(): Permanent cheat injection that resolves CPU virtual addresses to ROM file offsets (NES, SNES LoROM, Game Boy), validates compare bytes, and writes patch bytes atomically.
  • Arbitrary Bitstream I/O Primitives (miorom.core.bitstream):
  • BitReader: Arbitrary bitstream reader supporting MSB-first and LSB-first bit ordering across byte boundaries; provides read_bits(), read_signed_bits(), peek_bits(), skip_bits(), and align_byte().
  • BitWriter: Arbitrary bitstream writer supporting MSB-first and LSB-first accumulation, signed bit packing, and byte-boundary padding.
  • Planar Bitplane Tile Codec & Interleaver (miorom.graphics.planar):
  • PlanarTileCodec: Modular 8x8 indexed pixel tile encoder and decoder covering 1bpp mono, 2bpp Game Boy / NES / SNES, 3bpp Capcom SNES, 4bpp SNES planar, 4bpp Sega Genesis chunky, 4bpp GBA, and 8bpp SNES Mode 7.
  • split_bitplanes() / combine_bitplanes(): Isolates and recombines individual 8-byte bitplanes for layer-specific manipulation and font glyph injection.
  • planar_to_linear() / linear_to_planar(): Bulk stream converters between console planar tiles and raw indexed pixel streams.
  • Retro Bus Address & Banking Coordinate Translators (miorom.core.bus_mapper):
  • SNESBusMapper: Pure mathematical translations between 24-bit SNES bus addresses and physical ROM offsets for LoROM (Mode 20), HiROM (Mode 21), and SMC-headered ROMs.
  • NESBusMapper: Bus-to-offset mapping formulas for NROM (16KB/32KB), MMC1 16KB switchable PRG banks, and MMC3 8KB PRG banks (Modes 0 and 1).
  • GameBoyBusMapper: Cartridge ROM mapping for fixed Bank 0 ($0000-$3FFF) and switchable banks ($4000-$7FFF) with MBC1 bank translation quirks ($00, $20, $40, $60).
  • Variable-Length Integer Codec Enhancements (miorom.core.vlq):
  • Added encode_sqlite_varint() and decode_sqlite_varint() for 1-9 byte variable-length integers used in embedded database and savegame formats.
  • Added zigzag_encode() and zigzag_decode() for signed integer mapping.
  • Exposed module-level shortcuts: encode_vlq(), decode_vlq(), encode_uleb128(), decode_uleb128(), encode_sleb128(), decode_sleb128().
  • Retro Cyclic & Additive Checksum Primitives (miorom.core.checksum):
  • RetroChecksum: Zero-dependency, pure-Python bitwise checksum suite including CRC-16 (CCITT, XMODEM, ARC, Modbus), pure-Python IEEE 802.3 CRC32, Adler-32, Fletcher-16, Sega Genesis 16-bit big-endian sum, SNES complement checksum pair, and Game Boy header/global checksums.
  • Bitwise Arithmetic & Word Transformation Primitives (miorom.core.bits):
  • Bitwise rotations and reflections: rol() (rotate left), ror() (rotate right), bit_reverse8(), bit_reverse16(), bit_reverse32(), bit_reverse().
  • Nibble packing and extraction: swap_nibbles(), pack_nibbles(), unpack_nibbles().
  • Sign extension and bit counting: sign_extend() (for immediate values), popcount(), clz() (count leading zeros), ctz() (count trailing zeros).
  • Endianness byte swapping: bswap16(), bswap32(), bswap64().
  • Canonical Huffman Tree & Codec Primitives (miorom.core.huffman_tree):
  • HuffmanNode: Binary node structure for priority-queue frequency trees.
  • build_huffman_tree() / extract_code_lengths(): Builds optimal prefix trees from symbol frequency histograms and extracts bit lengths.
  • generate_canonical_codes(): Generates deterministic RFC 1951 / JPEG compliant Canonical Huffman bit codes.
  • Binary Slicing & Free Space Block Finder (miorom.core.slicer):
  • align_up() / align_down(): Memory and boundary alignment calculations.
  • pad_bytes(): Sector boundary padding with arbitrary fill byte.
  • chunk_bytes(): Generator yielding fixed-size contiguous slices across buffers.
  • find_free_blocks(): Scans binary buffers for contiguous sequences of padding bytes (code cave / free space discovery) with optional alignment constraints.
  • BinarySlicer: Zero-copy memoryview slice navigator with pattern matching and in-place chunk replacement.
  • Branch Displacement & Jump Opcode Math (miorom.asm.branch_calc):
  • ARM: calc_arm_branch() and resolve_arm_branch() for 32-bit B and BL opcodes accounting for 8-byte prefetch pipeline.
  • Thumb-16: calc_thumb_branch() and resolve_thumb_branch() for dual-instruction BL pairs (+/- 4MB).
  • MIPS: calc_mips_jump(), resolve_mips_jump() for 26-bit pseudo-absolute J/JAL targets; calc_mips_branch(), resolve_mips_branch() for 16-bit word-offset branches with delay slot calculation.
  • MOS 6502 / W65C816: calc_6502_branch() and resolve_6502_branch() for 8-bit signed relative displacements.
  • Sliding Window Ring Buffer & LZ Match Finder (miorom.core.ring_buffer):
  • RingBuffer: Circular sliding byte buffer with relative indexing, chronological window extraction, and copy_lz_match() handling overlapping RLE byte repeats.
  • LzssMatchFinder: Hash-chained longest match finder with automatic sliding window expiration pruning for proprietary LZ compression algorithms.
  • Shannon Entropy & Sliding Randomness Profiler (miorom.core.entropy):
  • calculate_entropy(): Computes Shannon entropy (0.0 to 8.0 bits/byte) to identify padding, code, audio, and compressed streams.
  • byte_frequency(): Generates exact byte-level frequency histograms.
  • sliding_entropy_scan(): Sliding-window entropy scanner mapping density profiles across large files.
  • chi_squared_test(): Uniform distribution test for cryptographic randomness.
  • find_entropy_regions(): Detects start and end boundaries of compressed or encrypted binary streams.
  • XOR & Rolling-Key Byte Deobfuscator (miorom.core.cipher):
  • xor_bytes(): Repeating multi-byte XOR encryption and decryption.
  • rolling_xor(): Rolling-key XOR stream cipher ($K_{i+1} = (K_i + step) \pmod{256}$).
  • invert_bytes(): Bitwise NOT unmasking.
  • add_cipher(): Additive / Caesar byte cipher modulo 256.
  • crack_single_byte_xor(): Automated single-byte XOR key cracker using printable character distribution heuristics.
  • Split-Bus & Interleaved Byte Arrays Reconstructor (miorom.core.interleave):
  • combine_split_words(): Merges separate low-byte, high-byte, and optional bank-byte arrays into 16-bit and 24-bit pointer words.
  • split_words(): Decomposes integer words across independent byte lanes.
  • deinterleave_channels() / interleave_channels(): Deinterleaves and interleaves multi-channel byte streams (arcade even/odd EPROMs and multi-channel audio).
  • Cumulative Delta & Relative Offset Resolvers (miorom.core.delta):
  • cumulative_offsets(): Converts entry lengths into contiguous start offsets ($P_i = P_{i-1} + L_{i-1}$).
  • offsets_to_lengths(): Inverts pointer tables into entry lengths.
  • delta_decode() / delta_encode(): Encodes and decodes differential numeric sequences.
  • relative_to_absolute() / absolute_to_relative(): Translates offsets relative to header/table bases.
  • Pointer Table Sequence & Monotonic Stride Validator (miorom.core.pointer_analyzer):
  • unpack_pointer() / pack_pointer(): 16-bit (2-byte), 24-bit (3-byte SNES/Neo-Geo far addresses), and 32-bit (4-byte) pointer word serialization for little-endian and big-endian architectures.
  • unpack_pointers() / pack_pointers(): Vectorized unpacking and packing for contiguous pointer arrays.
  • verify_stride_monotonicity() / calculate_monotonicity_ratio(): Evaluates strict and non-decreasing monotonic sequences.
  • analyze_pointer_sequence(): Generates PointerSequenceMetrics containing span, mean delta step, duplicate ratio, and composite statistical confidence score.
  • remap_pointers(): Remaps address targets via lookup table or uniform delta translation.
  • find_pointer_tables(): Automated binary scanner discovering bounded candidate pointer tables with multi-stride, endianness, and alignment analysis, returning deduplicated PointerTableCandidate objects.
  • Tilemap & Nametable Matrix Compositor (miorom.graphics.tilemap):
  • decode_nes_nametable() / encode_nes_nametable(): Full NES 32x30 nametable (960 bytes) and 64-byte 2x2 sub-block attribute table roundtrip codec.
  • decode_genesis_tilemap() / encode_genesis_tilemap(): Sega Genesis / Mega Drive VDP Plane 16-bit big-endian tilemap serialization.
  • decode_gbc_tilemap() / encode_gbc_tilemap(): Game Boy Color parallel VRAM bank 0 (tile index) and bank 1 (attributes) decoder/encoder.
  • render_pixels() / render_flat_pixels(): 2D/1D pixel compositing with tile flipping (H-flip, V-flip) and palette offset calculations.
  • submap() / paste(): Rectangular tilemap region slicing and stamping.
  • Script Delimiter & Control Code Auto-Detector (miorom.script.boundary_detector):
  • detect_delimiters(): Cross-references pointer table targets to detect 1-byte and multi-byte string terminators (0x00, 0xFF, 0xFE 0x00) with statistical confidence scoring.
  • detect_control_codes(): Identifies embedded bytecode opcodes within dialogue streams, measures frequencies, and estimates parameter argument lengths.
  • analyze_script_boundaries(): Generates ScriptBoundaryReport covering string length distributions, memory alignment, and candidate terminators/tokens.
  • slice_script_entries(): Extracts discrete binary text entries between targets with optional terminator stripping.
  • Metatile (16x16 / 32x32) Map Assembler (miorom.graphics.metatile):
  • MetatileTable: Manages 16x16 metatile definitions with sequential 8-bit (NES/GB), sequential 16-bit (SNES/Genesis), and planar 4-array (TL, TR, BL, BR) serialization.
  • MetatileMap: 2D metatile index grid expanding into full 8x8 Tilemap structures (to_tilemap()) and compressing from raw tilemaps (from_tilemap()).
  • Split-Bank & Dual-Table Far Pointer Relinker (miorom.core.far_pointer):
  • resolve_banked_to_offset() / resolve_offset_to_banked(): Bidirectional coordinate translators between physical ROM offsets and (bank, cpu_address) pairs for NES, Game Boy, and SNES LoROM/HiROM.
  • read_split_pointer_table() / write_split_pointer_table(): Manages dual parallel tables (1-byte banks + 2-byte CPU addresses).
  • read_interleaved_pointer_table() / write_interleaved_pointer_table(): Manages 3-byte contiguous far pointer arrays.
  • relocate_banked_table(): In-place relocation engine updating banked pointer records and calculating new bank/address mappings.

Fixed

  • Anti-Zip-Slip / Directory Traversal Protection Across Archive Handlers:
  • Integrated sanitize_extract_path into NDS ROM (NDSRom.extract, NDSRomHandler), GameCube (GameCubeRomHandler), and ISO9660 (Iso9660RomHandler) extraction pipelines, blocking arbitrary file writes outside destination directories.
  • NarcRomHandler.unpack() (miorom.rom.handlers.narc): e.name sourced from untrusted NARC archive metadata was passed directly to os.path.join without sanitization — completing the path traversal protection across all four archive handlers. NARC is the most frequently accessed NDS container format (MSG, graphics, data assets in virtually every NDS title).
  • SPU-ADPCM Negative Shift Exception Safety (miorom.audio.vag):
  • Added arithmetic right-shift handling when shift > 12, preventing ValueError: negative shift count crashes on corrupted/fuzzed audio.
  • Systemic Decompression Integrity & Truncation Detection (miorom.compression):
  • Added strict payload size validation on LZ10, LZ11, Yaz0, Yay0, RLE, and Huffman, raising CompressionError on unexpected EOF or truncated streams instead of silently corrupting downstream consumers.
  • Patcher Bounds Validation (miorom.patch.bps):
  • Guarded SourceCopy and TargetCopy in BpsPatcher against negative indexing and buffer overflows, raising PatchError.
  • DSP-ADPCM Filter Coefficient Bounds (miorom.audio.dsp_adpcm):
  • Added coefficient length validation (len(coefs) >= 16) in decode_frame and decode to prevent raw IndexError exceptions.
  • NES 2.0 Exponent Notation & Archaic iNES Convention (miorom.platforms.nes):
  • Implemented standard NES 2.0 exponent-multiplier notation (2^E * (multiplier * 2 + 1)) and handled archaic iNES 0-byte PRG size (representing 256 banks / 4MB).
  • PS1 TIM Texture Bounds Checking (miorom.platforms.psx.tim):
  • Added validation for CLUT and pixel image section lengths against true input buffer length, raising ParseError on truncated TIM textures.
  • Performance Optimization for Error-Diffusion Dithering (miorom.graphics.palette):
  • Inlined error diffusion in FloydSteinbergDitherer, eliminating inner loop closure allocations.
  • CharMap Strict Mode (miorom.text.charmap):
  • Added optional strict: bool = False to CharMap.encode(), raising ValueError on unmapped multibyte characters.
  • Low-RAM Streaming ROM Unpacking for Disc Images (miorom.rom.handlers):
  • Added unpack_file streaming implementation for GameCubeRomHandler and Iso9660RomHandler, enabling extraction of multi-gigabyte ISOs with minimal RAM usage.
  • XRef Database Export Interoperability (miorom.asm.xref_engine):
  • Added export_ida_map (IDA Pro .map) and export_ghidra_xml (Ghidra Program XML) to XRefDatabase.
  • Branch Validation Error Handling (miorom.asm.branch):
  • Imported ParseError in branch.py preventing NameError exceptions when validating unaligned branch targets.
  • ROM Handler Keyword Arguments Passthrough (miorom.rom.handlers):
  • Corrected unpack() forwarding in NDSRomHandler, GameCubeRomHandler, and Iso9660RomHandler to prevent duplicate filepath argument errors when delegating to unpack_file().
  • Streaming ROM Handler Filepath Probing (miorom.rom.handlers):
  • Enhanced can_handle in Iso9660RomHandler and GameCubeRomHandler to probe magic headers directly from filepath when passed empty in-memory buffer payloads.

[0.13.1] — 2026-09-10

Added

  • Flexible Buffer Support in PatchWriter (miorom.patch.patch_writer):
  • PatchWriter.__init__ now accepts Union[bytes, bytearray], automatically casting immutable bytes to a mutable bytearray when needed.
  • Documentation Design with MkDocs Material:
  • Added documentations.

[0.13.0] — 2026-09-10

Added

  • DMA Filesystem Abstraction (miorom.archive.dma):
  • Declarative DmaTableEntryStruct via BinaryStruct.
  • DmaTableArchive and DmaFileEntry for parsing, querying, extracting, and decompressing N64 DMA filesystems (Zelda OoT, Pokemon Stadium, Animal Forest).
  • MIPS COP1 (Floating Point) & Architecture Expansion (miorom.asm, miorom.script):
  • Added full MIPS COP1 float instruction decoder to UniversalDisassembler: lwc1, ldc1, swc1, sdc1, mfc1, mtc1, add.s, sub.s, mul.s, div.s, c.lt.s, bc1t, bc1f.
  • Added SM83 (Game Boy) and Motorola 68000 (Sega Mega Drive) instruction decoding and SSA IR lifting.
  • Added fluent MIPS snippet assembly builder methods (li, lw, sw, addu, subu, sll, srl, ori, andi, move, j, jal) to MipsSnippet.
  • Fast3D Microcode Pipeline (miorom.graphics.fast3d):
  • Fast3DParser: Discovers texture descriptors (F3DTextureDescriptor), dimensions, and formats from N64 microcode streams (G_SETTIMG, G_SETTILE, G_SETTILESIZE, G_LOADBLOCK).
  • Fast3DBuilder: Fluent Python emitter for N64 display lists (set_timg, set_tile, set_tile_size, load_block, end_dl).
  • N64 Fast3D Textures (miorom.graphics.n64_texture):
  • Pixel-accurate encoders and decoders for RGBA32, RGBA16 (5551), IA16, IA8, IA4, I8, I4, CI8, and CI4.
  • Dialogue Script & Hex Sanitizer (miorom.formats.script_catalog):
  • DialogueCleaner: Strips low-level pointer artifacts, garbage byte tags, and parses control codes to natural newlines.
  • ScriptCatalog: Bidirectional translation catalog manager in clean human-readable [id]\ntext format with synchronization back to CSV.
  • Universal Control Code Tokenizer (miorom.text.tokenizer):
  • Bidirectional serialization between binary control codes and markup tags (ControlCodeSchema, ControlCodeTokenizer, ControlCodeDef).
  • Far-Memory Layout Allocation (miorom.patch.slack):
  • FarMemoryHeap: Manages memory allocation in expanded ROM address spaces (e.g. 32 MB -> 64 MB) with alignment and boundary enforcement.
  • Constant-Memory Streaming Patcher (miorom.patch.ips):
  • IpsPatcher.apply_stream(): Stream-based chunked patching (<64 KB RAM) for multi-gigabyte disc images (Wii/GameCube/PS1).
  • Segmented Address Resolution (miorom.core.pointer):
  • SegmentTable and SegmentedAddressResolver for two-way translation of segmented virtual addresses (0x07xxxxxx, KSEG0, SNES banks) to physical ROM offsets.
  • N64 Audio & Sequences (miorom.audio.n64_seq):
  • M64Sequence (N64 Compact MIDI command parser) and N64Audiobank (instrument sample metadata).
  • Universal Save File Checksum Engine (miorom.save.checksum):
  • SaveChecksumEngine.verify_and_fix() supporting SRAM, EEPROM, and FlashRAM save files.
  • Symbol Table Interop (miorom.core.symbol_map):
  • Added export methods: to_ghidra_script() (Python Script Manager), to_ida_idc() (IDA Pro IDC), and to_sym() (No$GBA / PCSX symbol format).
  • Automated Function Boundary Detection (miorom.asm.prologue_scanner):
  • FunctionPrologueScanner: Bitmask preamble scanning for MIPS (addiu $sp), ARM (push {lr}), and PowerPC (stwu r1).
  • Proportional Font Kerning & Textbox Collision (miorom.text.vwf):
  • VWFMetricsInspector: Kerning pair tracking and automated dialogue overflow detection against textbox boundaries.
  • MIPS Compound Relocation Matching (miorom.link.relocator):
  • CompoundRelocationLinker: Proper sign-extension carry bit compensation for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
  • Native Color Quantization (miorom.graphics.palette):
  • FloydSteinbergDitherer: Pure-Python error-diffusion dithering for palette reduction without external dependencies.
  • Bytecode Interpreter Profiling (miorom.script.vm_profiler):
  • VMBytecodeSynthesizer.detect_dispatcher_tables(): Automated jump table discovery for custom virtual machine opcode interpreters.
  • Semantic Patch Safety Validation (miorom.diff.patch_auditor):
  • PatchAuditor: Validates IPS/BPS patch hunks against critical ROM memory regions (headers, DMA descriptors, vectors) to prevent asset collision.
  • Declarative Schema Constraints (miorom.core.schema):
  • SchemaField(validate=...): Custom validation hooks on struct fields with structured ParseError exceptions.
  • Emulator Database Compatibility (miorom.platforms.n64):
  • Added preserve_database_crc parameter to N64Rom.recalculate_checksum() and fix_n64_checksum() to retain No-Intro database matching for emulator boxart and profiles.

Changed

  • Standardized CsvHandler.export_csv() and export_clean_csv() to automatically ensure parent directories exist before writing.

[0.12.0] — Initial Release

  • Core ROM hacking toolkit: scanning, patching, compression, disassembly, platform handlers.