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 viamiorom.core.schema.BinaryStructensuring unified binary schema compliance without rawstructcalls. - 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 formulachk = (SUM_{0xA0..0xBC} - 0x19) & 0xFF. - Added entry point decoding (
entry_point), resolving the 24-bit relative ARM branch opcode at offset0x00to 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-accurate0xFFpadding (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 intodetect_save_type()andGBARomHandlermetadata. - 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 withSappyScannerfor 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
GBARomHandlerinRomManagerwith 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 viamiorom.core.schema.BinaryStructensuring unified binary schema compliance without rawstructcalls. - 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\x01CD001at 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.CNFparser 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 withPSXExefor 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
PSXRomHandlerinRomManagerwith 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 (0xFFFEextensible 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.BinaryStructensuring unified struct processing without rawstructoperations. - 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 scannerfind_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.BinaryStructensuring unified binary parsing and serialization. - Added early encrypted executable detection (
b"~PSP"signature) raising informative error guidance directing users to unencryptedPSP_GAME/SYSDIR/BOOT.BIN. - Added Sony
sceModuleInfo52-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 acrosssceDisplay,sceCtrl,sceIo,sceKernel,sceAudio,sceFont,scePower,sceUtility,sceRtc,sceGe, andsceUmd. - Implemented MIPS function call stub redirection (
redirect_stub()) generating bit-exact MIPSj <target>; nopopcodes 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)andsave(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 customizeICON0.PNG,PIC1.PNG, andSND0.AT3without 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 ofBOOT.BINandEBOOT.BIN. - Added deep texture scanning (
find_textures()), traversing disc filesystem paths to identify and parse embeddedGIMImagetextures. - Enhanced
CSOImagewithto_iso_bytes()and introducedCSOCompressorfor standalone pure-Python CSO compression and decompression. - Registered
PSPRomHandlerinRomManagerwith 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 viamiorom.core.schema.BinaryStructensuring unified struct processing without rawstructoperations. - 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, addingexport_nsbtx()/export_nsbtx_file()to dump embeddedTEX0sections to standalone.nsbtxfiles, andimport_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-blockMDL0-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 embeddedTEX0block. - Enhanced
NSBTXFilewithfrom_tex0_bytes()andto_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 viamiorom.core.schema.BinaryStructensuring unified struct processing without rawstructoperations. - 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()) computingcrc_v1(covering 0x0020..0x083F),crc_v2(0x0020..0x093F), andcrc_v3(0x0020..0x0A3F) viaRetroChecksum.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) viamiorom.core.schema.BinaryStructensuring unified struct processing without rawstructoperations. - 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 offset0x0EviaRetroChecksum.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
blzcodec inmiorom.compressionregistry 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
NDSRomwith ARM9 compression diagnostics and decompression utilities (is_arm9_compressed(),decompress_arm9(),compress_arm9(), andset_arm9_binary()), providing seamless ARM9 code modification and repacking workflows for translators. - Enhanced
NDSOverlayCompressorinmiorom.platforms.nds.overlayto 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, magicMsgFlwBn) 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.BinaryStructensuring unified binary packing/unpacking and zero redundant struct logic. - Implemented high-level directed node graph model transforming raw 16-byte
FLW3nodes and branch table jump indices into intuitive object-oriented nodes (MessageNodefor dialogue lines,ChoiceNodefor branching player decisions,EventNodefor script triggers). - Added bidirectional MSBT linking (
link_msbt()) automatically cross-referencing message node indices withMSBTFilelabels 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 standardflowchart TDdiagrams 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, magicTHP\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.BinaryStructensuring 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 PCMWavSoundstream. - 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 ofmax_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, magicRSAR) 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 (
RSARheader,SYMBsymbol table,INFOparameter tables, andFILEpayload section), resolving developer symbol strings to internal Sound IDs and File IDs. - Implemented analytical
RWSDFile(Revolution Wave Sound Data) sub-file handler parsingWSD1wave descriptors andDATAaudio 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 PCMWavSound/ 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 acrossRSARHeaderStruct,INFOHeaderStruct, and theFILEsection. - 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
IMETheaders 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
IMD5wrapper parsing and automatic re-wrapping with payload MD5 recalculation formeta/banner.bin,meta/icon.bin, andmeta/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) andBNR2(6-language) banner support with 96x32 RGB5A3 hardware micro-tile image decoding and encoding viaPNGImagewith 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()) andWiiRomHandler(extractingsys/opening.bnron 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, magicRLYT) 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) andpae1(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
txt1panes with UTF-16-BE string encoding, dynamic RAM buffer capacity expansion (str_buf_len), and buffer overflow protection (expand_buffer=Falseguard). - 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 viaBRFNTFont) with automaticfnl1table 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, magicRFNT) and Nintendo DS NFTR (.nftr, magicFONT) 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()) viaPNGImagewith 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 automaticCMAPandCWDHregistration. - 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}.xmland{mod_name}/...). - Implemented in-memory virtual disc patching (
apply_riivolution_to_disc()) applying<file>,<folder>, and surgical<memory>patches directly to decryptedWiiDiscpartitions andmain.dolexecutables, allowing modders to repack patched games into standalone.iso,.wbfs, or.rvzROMs without physical consoles. - Added unified CLI subcommands in
miorom patch:export-riivolution(diffing & package generation) andapply-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, magicRVZ\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")), andGameCubeRomHandler(unpack,repack(format="rvz")). - Added instant format discrimination in
RomManagerchecking the 128-bytedheadheader without decompressing disc payload, seamlessly routing Wii RVZs toWiiRomHandlerand GameCube RVZs toGameCubeRomHandler. - Nintendo Wii ROM Handler & CLI Integration (
miorom.rom.handlers.wii/miorom.rom): - Implemented
WiiRomHandlerconnectingWiiDiscandWBFSDiscengines directly intoRomManagerand the unified CLI pipeline (miorom unpack,miorom pack). - Added automatic container format detection via
WBFSmagic (offset 0x00), Wii Disc magic0x5D1C9EA3(offset 0x18), and partition table validation at0x40000. - Added structured extraction into
sys/(header.bin,boot.bin,bi2.bin,apploader.img,main.dol,ticket.bin,tmd.bin) androot/(all FST virtual game assets), generating completemiorom.meta.jsonmanifests. - 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
.isoand sparse.wbfsoutput 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, magicRSTM) 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
WavCodecandWavSound(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, magicRLAN) 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
0x40000supporting 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 nestedfiles/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
.isoand sparse.wbfscontainers. - 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 (
MDL03D models,CHR0skeletal animations,CLR0,PAT0,SRT0,SHP0,SCN0) stored inother_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_offsetcalculations. - Added bidirectional
to_image(),to_png(), andfrom_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: bytesortitle_key: byteson demand, while raw chunks remain fully inspectable without keys. - Implemented seamless in-memory bridge between WAD contents (
.appfiles) andU8Archive(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
U8Archivewith 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), andINDEX8(256-color CLUT) with deterministic chunk serialization (to_bytes()) and bidirectional conversion toPNGImage/ 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()) inPBPFile. - 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.schemaserializations. - 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(), andsummary(). - 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(), andDolBinary.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 withUniversalDisassembler. - Zero-Dependency RIFF/WAVE Audio Codec (
miorom.audio.wav_codec) & Unified Audio CLI (miorom audio): - Implemented standalone
WavCodecandWavSoundcontainer using purelymiorom.core.schemawith 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 centralizedADPCMCodec.build_wav/read_wavthroughWavCodec. - Added
miorom audioCLI suite withconvert(bidirectional transcoding between WAV, VAG, BRR, and DSP),extract(dumping WAVs from SPC audio RAM and SDAT sound archives), andinfo(inspecting sample rate, bit depth, channel layout, sample count, duration, and container headers with optional--jsonoutput). - 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, andMemoryCard(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 hierarchicalmiorom patchsubcommands (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 standaloneinspect_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 Pythonstructimports across all 114 engine modules insrc/miorom. All binary deserialization, packing, bytefield definitions, and endian-aware operations are now routed natively throughmiorom.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 rawstructoutside of the core schema engine.
Fixed¶
- Nintendo MSBT Text Engine
MSBTFile— UTF-8 control tag\x0Estring truncation on null arguments,ATR1attribute 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 withsec_data.find(b"\x00", off). In official Nintendo MSBT format, control tags in UTF-8 begin with0x0E, followed by a 6-byte header (group ID, tag ID, and argument length as uint16) andarg_lenargument 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 skipping7 + arg_lenbytes in UTF-8 mode, ensuring string parsing only terminates at authentic dialogue null delimiters. ATR1Attribute Section Omission & Dropped Message Attributes:MSBTEntrydeclaresattributes: bytes = b"", butMSBTFile.from_bytes()completely ignoredATR1sections, leaving all message attributes empty. Furthermore,to_bytes()hardcodedsection_count = 2, droppingATR1and destroying any custom sections (e.g.TSY1style tables,NLI1,ATO1), which caused games like Super Mario Galaxy and The Legend of Zelda to crash upon loading repacked MSBTs. Fixed by parsingATR1entries intoMSBTEntry.attributes, generatingATR1duringto_bytes()whenever attributes are present, preserving all unrecognized sections inother_sections, and dynamically computingsection_count = len(sections).- Unlabelled Dialogue String Loss:
from_bytes()previously built entries solely by iterating overlabels. If an MSBT file contained strings inTXT2without corresponding labels inLBL1(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 fallbackSTR_{i}. Added regression tests intests/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 polynomial0xA001(CRC-16-IBM / Modbus). Official Nintendo DS hardware and ARM7/ARM9 BIOS Boot ROM SWI 0x0E verify the cartridge header at offset0x15Ecovering bytes0x000..0x15Dusing reflected polynomial0x8408(CRC-16-CCITT / Kermit). Consequently,NDSRom.verify_header_checksum()returnedFalseon valid retail NDS ROMs, ROMs repacked byNDSRomHandler, and ROMs repaired byRomIntegrityManager. Moreover,NDSRom.fix_header_checksum()wrote the incorrect0xA001checksum into offset0x15E, rendering the ROM unbootable on authentic DS hardware and emulators (DeSmuME, melonDS). Fixed by addingRetroChecksum.crc16_nds()(poly0x8408, init0xFFFF), unifyingcalculate_nds_crc16()acrossrom.py,handlers/nds.py, andintegrity.py, and addingis_header_checksum_valid()toNDSRom. Added regression tests intests/test_nds.py. - IPS Patcher
IpsPatcher— Silent byte loss at offset0x454F46(b"EOF"),apply_streamoverlapping 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 offset0x454F46(the ASCII value of"EOF"), the code previously didstart = 0x454F47; i = 0x454F47. This jumped over byte0x454F46, silently omitting the user's change at that exact offset and leaving it unpatched. Fixed by stepping back 1 byte tostart = 0x454F45and prependingmodified[0x454F45]todiff_bytes, safely bypassing theEOFcollision without discarding byte0x454F46. apply_streamOverlapping Records Desynchronization & Truncation: InIpsPatcher.apply_stream(), hunks were sorted but not merged viamerge_patches(). When an IPS patch contained overlapping records,current_pos < hunk.offsetevaluated toFalseandoutput_stream.write(hunk.data)wrote at the current stream pointer without seeking back, shifting subsequent file data and corrupting the output stream. Additionally,apply_streamignored the optional 3-byte truncation offset afterEOF. Fixed by merging hunks withmerge_patches()and strictly enforcingtruncate_size. Added regression tests intests/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/2352tracks (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 overheader + chunk(2052 bytes), omittingSYNC_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 oversector[: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 intests/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 astitle_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 prefix00010000(b"\x00\x01\x00\x00"). Fixed by formatting Title ID asb"\x00\x01\x00\x00" + header.game_id[:4]. - Non-Destructive In-Memory Banner Staging:
repack()previously wroteopening.bnrdirectly toroot_dir/opening.bnron disk, mutating the user's project files during repack. Fixed by stagingpart["files/opening.bnr"]in memory. Added regression tests intests/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_lbacalculated fromlen(self.data) // 2048desynchronized from the actual appended byte offset. Fixed by paddingself.datato a 2048-byte sector boundary before calculatingtarget_lbaand extending data. Added regression tests intests/test_iso9660_builder.py. - Nintendo GameCube
GameCubeRomHandler— RVZ unpackdisc_base.binomission producing unbootable repacked discs, extraction ofsys/main.dol, and modified executable & header injection during repack (miorom.rom.handlers.gc): - RVZ Unpack
disc_base.binOmission: InGameCubeRomHandler.unpack_rvz(),sys/header.binand virtual files inroot/were extracted, butsys/disc_base.binwas omitted. When repacking from this directory,repack()failed to finddisc_base.binand fell back to synthesizing a 0x450000-byte zeroed disc base, wiping out the apploader,bi2.bin, and the executablemain.dol, producing an unbootable disc. Fixed by extractingsys/disc_base.binup tofst_offsetdirectly from the RVZ disc stream. - Extraction of
sys/main.dol: Inunpack(),unpack_file(), andunpack_rvz(),sys/main.dolwas never extracted as a standalone file, impeding disassembly, reverse engineering, and ASM hacks. Added automated detection and extraction ofsys/main.dolusingDolBinaryto calculate exact text/data section bounds. - Repack Injection of Modified
sys/main.dol&sys/header.bin:repack()previously ignored changes made tosys/main.dolandsys/header.binwheneverdisc_base.binwas present. Enhancedrepack()to detect updatedsys/header.bin(refreshingdisc.headerand sector 0) and updatedsys/main.dol(injecting the patched binary atdol_offsetand safely elevatingfst_offsetif the executable expanded). Added regression tests intests/test_platforms_gc.pyandtests/test_platforms_iso_rvz.py. - Sony PlayStation 1 & PSP
PSXRomHandlerandPSPRomHandler—TypeErrorcrash on filepath unpack, signature alignment, and filesystem clue format detection (miorom.rom.handlers.psx,miorom.rom.handlers.psp,miorom.rom.manager): TypeErroron Filepath Unpack:PSXRomHandler.unpack()andPSPRomHandler.unpack()previously declared signatures(self, data: bytes, output_dir: str)without**kwargs. When unpacked viaRomManager.unpack(filepath, ...)or CLImiorom unpack,filepath=filepathwas passed as a keyword argument, raisingTypeError: unpack() got an unexpected keyword argument 'filepath'and crashing execution. Fixed by adding**kwargsto both handlers and leveragingPSXRom.from_file()/PSPRom.from_file()when streaming from disk.- Repack First Parameter Alignment & Format Inference Clues: Aligned the first parameter of
repack()toinput_dirmatchingBaseRomHandler, and added filesystem detection clues inRomManager.repack()forSYSTEM.CNF(psx),PSP_GAME(psp), andticket.bin/tmd.bin(wii) to prevent unpacked directories from misidentifying as"u8". Added regression tests intests/test_platforms_psx_rom.pyandtests/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) or0x1A(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 color0, 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 withb"\x00\x00"orb"\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_idandinf_hdr.default_colorwere dropped, and message attributes (sec_reader.skip(inf_hdr.entry_size - 4)) were discarded, leaving all messages with emptyattributes = b"". InBMGFile.to_bytes(),entry_sizewas hardcoded to 8, corrupting games that useentry_size = 4(no attributes) orentry_size = 12(Twilight Princess with 8-byte attributes), and attributes were replaced with zeroes (inf_body.write_u32(0)). Fixed by capturingfile_id,default_color, andinf_entry_size, reading and populatingBMGMessage.attributes, and dynamically writing preserved or custom attributes. Added regression tests intests/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 address0x00200001, end address0x00200FFF, backup RAM flags0xF8 0x20, and memo fields).MDHeader.parse()previously checked onlysram_support = _reserved_0xB0[:2] == b"RA"and discarded the entire 64-byte block. When saving or packing (MDHeader.pack()/MDRom.to_bytes()),reservedwas allocated as zeroes with onlyb"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 withMDRom. Fixed by storingreserved_b0: bytes, preserving all 64 bytes of SRAM settings across roundtrips, addingsram_startandsram_endproperties, and providingconfigure_sram()for non-SRAM ROMs. Added regression tests intests/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).GCCommentStructhardcodedencoding="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 inGCCommentStruct, 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 indexingrgba[idx], causing unhandledIndexErrorwhen images with custom or smaller dimensions were supplied. Added explicit buffer length guards and enhancedGCBanner.set_image()to acceptPNGImage,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) toWiiBanner.get_title()andset_title()allowing human-readable names ("japanese","english","ja") and integer indices (0..9), and updatedto_bytes()to truncate oversized UTF-16 strings along character boundaries rather than slicing raw bytes. Added regression tests intests/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 usingif rgb_q in palette[1:]: pixel_indices[y][x] = palette.index(rgb_q). Becausepalettealways 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) evaluatedpalette.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 queryingchosen_opaquedirectly and assigning1 + chosen_opaque.index(rgb_q)for opaque pixels, strictly restricting index 0 to transparent pixels (alpha < 128). NDSRomHandler.unpack()Multilingual & DSi Banner Truncation: InNDSRomHandler.unpack(), extractingbanner.binhardcoded0x840bytes (data[rom.banner_offset : rom.banner_offset + 0x840]). For v2 (Chinese,0x940bytes), v3 (Korean,0xA40bytes), and DSi (0x23C0bytes) 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 fullb_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 thetitlesdictionary withversion=1,num_langsremained 6, silently omitting the Chinese and Korean titles from the binary output. Fixed by checkingself.titlesto automatically elevatenum_langsto 7 or 8 and bumpself.versionto 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 updatedto_bytes()title serialization to truncate oversized strings along character boundaries rather than slicing raw bytes. Added regression tests intests/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..0x43allocates 64 bytes for Shift-JIS title text.PSXSaveHeaderStructpreviously divided this region intotitle_raw = RawBytes(32)and_reserved_title_pad = RawBytes(32). When reading,_parse_save_contentonly decodedtitle_raw(ignoring bytes 33..64), andbuild_block_data()truncated encoded titles at[:32], actively destroying long Japanese game titles (e.g.ファイナルファンタジーVII クラウド レベル99at 43 bytes was truncated toファイナルファンタジーVII クラウ). Fixed by declaringtitle_raw = RawBytes(64)inPSXSaveHeaderStructand supporting full 64-byte Shift-JIS titles across_parse_save_content,build_block_data(), and.mcssingle-save file export/import. - Multi-Block Directory Entry Specification Compliance: In
PSXMemoryCard.inject_file(),file_sizeandfilenamewere 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 containfile_sizeandfilename; middle blocks (0x52/IN_USE_MIDDLE) and last blocks (0x53/IN_USE_LAST) must zero outfile_sizeand leavefilenameempty (b""). Writing non-zero file sizes and filenames on chained blocks caused potential directory corruption and parsing abnormalities in BIOS and emulators. Fixed by settingfile_sz = 0andfname_bytes = b""for middle and last blocks. - Dynamic Payload Expansion & Block Recalculation: When
save.payloadwas modified to be larger after creation,build_block_data()truncated data to the oldself.block_count * BLOCK_SIZEwithout expanding blocks, silently dropping user data. Fixed by automatically recalculatingneeded_blocksand updatingself.block_countandself.file_size = actual_file_sizeon serialization. - Delete Safety & Infinite Loop Protection: Enhanced
delete_file()with block index bounds checks (1 <= start_block <= 15), validation thatstart_blockis an initial block (PSXBlockState.IN_USE_INITIAL), andvisitedcycle protection to prevent infinite loops on corrupted card directory chains. - Convenience Query Methods: Added
get_file(filename)andhas_file(filename)toPSXMemoryCardfor case-insensitive save retrieval. Added regression tests intests/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 assignediso_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.isoextension (rom.save("game.iso")),self._isowasNone, causingto_bytes()to returnself._pbp.to_bytes(). This resulted in writing raw\x00PBPcontainer bytes into.isofiles 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 fromDATA.PSARor synthesize a valid ISO 9660 disc image for homebrew PBPs withDATA.PSPand metadata. - Unmounted Embedded ISO Filesystem in PBP: In
PSPRom.__init__, loading a PBP container initializedself._pbpbut never checkedDATA.PSARfor an embedded ISO 9660 image, leavingself._isoNone. As a result,list_files()only returned top-level PBP section names instead of virtual game assets (PSP_GAME/...), andhas_file()/get_file()failed to read game files from official PSN PBP releases. Fixed by detecting ISO 9660 PVD magic (\x01CD001) inDATA.PSARand mountingself._iso = ISO9660(psar_data). - Bidirectional Synchronization on Modification: Enhanced
replace_file(),set_title(),set_icon(),set_background(), andset_boot_audio()to automatically update both the PBP sections and the embeddedDATA.PSARISO image when both are active. - Subdirectory Filename Matching Boundary Bug: In
_find_iso_file(), the search loop previously matched viaf.strip("/").upper().endswith(target). In games where an asset shared a suffix with a system file (e.g.REBOOT.BINandBOOT.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 intests/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 hardcodedsec[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 (wheresec[15] == 0x01) do not possess an 8-byte subheader, and store user data atsec[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 causingISO9660mounting to fail (ParseError: Failed to mount PlayStation 1 ISO 9660 filesystem). Fixed by inspectingmode = sec[15]per sector and extractingsec[16:2064]for Mode 1 andsec[24:2072]for Mode 2 Form 1. - Mode 1 PVD Detection at Sector 16: In
_detect_sector_size_and_mode(), the detector only checkedsec16_off + 24forISO_PVD_MAGIC. In Mode 1 raw BIN images, the PVD is located atsec16_off + 16. Updated detection logic to check bothsec16_off + 24(Mode 2 Form 1) andsec16_off + 16(Mode 1), returning(2352, 1)and settingPSXFormat.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 updatedto_bytes(),generate_cue()(TRACK 01 MODE1/2352), andsave()to correctly serialize and preserve Mode 1 format. - Dynamic SYSTEM.CNF Cache Desynchronization: In
PSXRom.replace_file(), replacingSYSTEM.CNFwith updated configuration (e.g. modified boot executable or game ID) updated the virtual ISO filesystem but leftself.game_id,self.boot_path,self.region, andself.system_cnfcached with stale values. Fixed by dynamically callingself._parse_system_cnf()wheneverSYSTEM.CNFis replaced. Added regression tests intests/test_platforms_psx_rom.py. - GameCube/Wii
RVZDisc& PSPPRXModule— 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 usingbio.seek(total_rvz_size). In Python'sio.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 thantotal_rvz_size, desynchronizing the file size fromfile_head.wia_file_sizeand violating Dolphin's WIA/RVZ container specification. Fixed by explicitly zero-paddingbytearray(bio.getvalue())tototal_rvz_size. - RVZ Stream Resource Management & Hash Verification: Added
close(), context manager support (__enter__/__exit__), andis_hash_valid()toRVZDiscto 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 ignoredstub_struct.var_count, leavingimp_lib.variablescompletely empty on any module importing external variables (e.g.errno, hardware registers). Fixed by parsing variable NID tables andvstub_table(present whenstub_len >= 6), implementingfind_import_variable(), and updatingreplace_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
PSPNIDResolverand persist human-readable symbol names onPRXImportFunctionandPRXImportVariableinstances. - PSP ATRAC3 / ATRAC3plus Audio Engine & Wii
U8Archive— RIFF container size desync truncation, inactivecSampleLoopsloop 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(), orreplace_data()mutatedaudio.raw_datain memory, the 32-bit RIFF file size field at byte offset 4..7 andriff_header.file_sizeremained at their old lengths. BecauseAT3Audio.__init__clipsraw_datatoriff_header.file_size + 8, passingaudio.raw_datadirectly to downstream consumers or re-parsing without explicitly callingto_bytes()truncated the appended 68-bytesmplchunk or expanded audio frames. Fixed by introducing_sync_riff_size()and updating both container header bytes andriff_header.file_sizeautomatically across all mutation methods. - Inactive
cSampleLoops == 0In-Place Loop Loss: When updating an existingsmplchunk that hadcSampleLoops == 0(e.g. sampler metadata without active loop points),set_loop()updated start and end offsets but leftcSampleLoopsat 0. Subsequent inspections or re-parses droppedloop_pointbecausecSampleLoops < 1. Fixed by automatically updatingcSampleLoopsto 1 inset_loop(). - Input Bounds Validation: Added explicit
ValueErrorvalidation inset_loop()ensuringstart_sample >= 0,end_sample > start_sample, andend_sample <= total_samples(when known), preventing low-level unsigned conversion errors or invalid loop points beyond the audio length. WAVE_FORMAT_EXTENSIBLEATRAC3 GUID Recognition: In_parse_format(),WAVE_FORMAT_EXTENSIBLEonly checked forATRAC3PLUS_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 validationoffset + 8 + csize <= limitto raiseParseErrorwhen a corrupted chunk header claims a size exceeding the container boundary. U8Archive.get()Mapping Parity: Added.get(path, default=None)onU8Archivefor consistent dictionary-like retrieval across all Nintendo archive classes.- Wii Riivolution Engine &
WiiPartition—DolBinarymissing import crash, sub-file offset redirectAttributeError,resize/create/recursivespecification violations, andorig_filelayout mismatch (miorom.platforms.wii.riivolution,miorom.platforms.wii.disc): DolBinaryMissing Import & Invalid Method Call inMemoryPatch: Inapply_riivolution_to_disc(), memory patching importedfrom miorom.platforms.gc.dol import DolBinary.DolBinarydoes not exist inmiorom.platforms.gc.dol(the module definesDolFile), causing an unhandledImportErrorwhenever applying memory patches tomain.dol. Furthermore, it attempted to calldol.write_address(mem.offset, mem.value), which does not exist on either class, and completely omitted verifyingmem.originalbytes against current RAM before writing. Fixed by importingDolFile, verifyingmem.originalviadol.read_memory()prior to patching, applying writes viadol.write_memory(), and updatingpartition.main_dolwithdol.to_bytes().WiiPartition.get()Missing Dict Method & Offset Redirect Crash: When applyingFileRedirectwithoffset > 0,apply_riivolution_to_disc()calledpartition.get(norm_disc_path, b""). BecauseWiiPartitiononly implemented__getitem__without a.get()method, partial file redirections raisedAttributeError: 'WiiPartition' object has no attribute 'get'. Fixed by implementing.get(path, default=None)onWiiPartitionfor safe dictionary-like retrieval.resize="false"andcreate="false"Specification Compliance:apply_riivolution_to_disc()previously ignored bothf_redirect.resizeandf_redirect.create(as well asf_folder.create). Fixed by enforcing thatcreate="false"redirects do not create new files on the disc, andresize="false"truncates oversized replacements or zero-pads undersized replacements to preserve original file lengths.FolderRedirect.recursive="false"Traversal Bug: Whenrecursive="false",os.walk(src_folder)previously traversed all nested subdirectories. Fixed by clearingdirnamesduring traversal whenrecursive=False, restricting folder mapping strictly to top-level files.- Unpacked Directory Hierarchy Asymmetry in
create_riivolution_package(): Whenoriginal_rootunpacked assets withroot/orfiles/butmodified_rootunpacked flat (or vice versa),orig_file = orig_path / rel_pathfailed 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, andmax_buffer_sizebuffer 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 * 2bytes 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 implementingTHPStereoAudioFrameHeaderStruct(80 bytes), demuxing and decoding both Left and Right channels with their respective coefficient tables inexport_audio_wav(), and preserving/encoding true stereo audio inset_audio_wav(). max_buffer_sizeFrame Header Sizing & Console Buffer Overflow: InTHPMovie.to_bytes(),buf_sizewas previously computed aslen(f.image_data) + len(f.audio_data), omitting the 16-byteTHPFrameHeaderStruct. According to the Nintendo THP format specification and DVD streaming architecture,max_buffer_sizespecifies the maximum buffer in bytes required to store one complete frame slice (header + video + audio) allocated in console memory byTHPPlayerInit. Omitting the 16 bytes produced undersized buffer allocations resulting in 16-byte buffer overflows on console hardware. Fixed by computingbuf_size = THPFrameHeaderStruct.sizeof() + len(f.image_data) + len(f.audio_data)(i.e.max(frame_sizes)). Added regression tests intests/test_platforms_wii_thp.py.BNSAudio 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 use2.decode_bns_to_wav()previously checked onlyelif codec == 2:, falling through to theelse:branch which synthesized 1000 samples of silence. When reading opening banner sounds from retail Wii discs, all audio was discarded. Fixed by recognizing both standard0and2as 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
0x18points 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 to0x20 + ch * 0x30without following the Channel Info pointer table. Fixed by formatting full 12-byte Channel Info structures inencode_wav_to_bns()withcodec = 0, and dynamically parsingchannel_data_offsetandchannel_dsp_offsetfrom the pointer table indecode_bns_to_wav(). Added regression tests intests/test_platforms_wii_banner.py. NDSBanner&extract_rom— DSi Extended Animated Icon offset misalignment, missingcrc_dsicalculation/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 at0x0A40..0x123F.NDSBanner.to_bytes()previously appendeddsi_extended_dataimmediately at0x0A40without the 0x800 bytes of zeros, shifting the animation payload 2048 bytes too early (yielding 0x1BC0 bytes) and breaking DSi firmware compatibility. Infrom_bytes(), reading authentic 0x23C0 DSi banners sliced from0x0A40:, capturing 6528 bytes (including the 0x800 zero padding) intodsi_extended_data. Fixed by padding to offset 0x1240 and 0x23C0 into_bytes(), and cleanly extracting0x1240:0x23C0(4480 bytes) infrom_bytes()while maintaining backward compatibility for legacy/unpadded buffers. - Missing
crc_dsiChecksum Calculation & Validation:header.crc_dsi(covering0x1240..0x23BF, 0x1180 bytes) was previously ignored duringto_bytes(recalc_crc=True)(remaining 0) andis_crc_valid()never verifiedcrc_dsi. Fixed by computingRetroChecksum.crc16_modbusacross the 0x1180 DSi payload during serialization and validating it inis_crc_valid()for Version 0x0103 banners. - Zero-Dependency PNG Export & Injection: Added
to_png_bytes()andsave_icon_png(path)toNDSBannerusing the pure-Python standard-libraryPNGCodec, and enhancedset_image()to accept PNG binary bytes,PNGImage, and PNG file paths without requiring Pillow. - ROM Extraction Banner Truncation:
extract_rominmiorom.platforms.nds.rompreviously extracted a hardcoded 0x840 bytes forbanner.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 intests/test_platforms_nds_banner.py. BRSTMFile— HEAD Part 1 0x34-byte sizing, missingfinal_block_padded_size, and relative audio offset desynchronization (miorom.platforms.wii.brstm):- 0x34-Byte HEAD Part 1 Structure & Missing
final_block_padded_size:BRSTMHeadPart1Structwas 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-bytefinal_block_padded_sizefield at offset0x28was omitted, causingsamples_per_seek(0x2C) andseek_entries_per_block(0x30) to shift backwards into offset0x28and0x2C, and truncating Part 1 by 4 bytes. Into_bytes(), this shifted Part 2 (p2_off) and Part 3 (p3_off) offsets by 4 bytes (e.g.p2_off = 0x44instead of0x48), producing non-standard BRSTM files that failed on hardware and third-party tools. Infrom_bytes(), authentic BRSTMs suffered field corruption and could not readfinal_block_padded_size. - Absolute vs Relative
audio_data_offset: Into_bytes(),audio_data_offsetwas hardcoded to0x20instead of the absolute file offsetdata_offset + 0x20. On Wii hardware and decoders like vgmstream that rely onaudio_data_offsetas an absolute seek target from file start, this caused DMA to stream from offset 0x20 (the RSTM header), resulting in static noise or crashes. Infrom_bytes(), reading authentic BRSTMs whereaudio_data_offset >= data_offsetmistakenly addeddata_off + p1.audio_data_offset, doubling the offset and skipping past the audio stream. Fixed by addingfinal_block_padded_sizeat offset 0x28, aligning Part 1/2/3 offsets to 0x34 bytes, computing absoluteaudio_data_offset = data_offset + 0x20into_bytes(), and handling both absolute and relative audio offsets infrom_bytes(). Added regression tests intests/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"evaluatedused_endian == ">"first, forcing"utf-16-be"for all Big-Endian archives (Nintendo Wii/GameCube standard) even whenencoding == "utf-8", while appending a single-byte0x00null terminator. Infrom_bytes(), the same operator precedence bug forced"utf-16-be"decode while scanning for single-byte0x00, 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.param1was hardcoded to0instead of recording the option offsetlen(all_options), and infrom_bytes(), everyChoiceNodeslicedoption_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\x00terminators in UTF-16, and properly indexing/tracking option offsets across ChoiceNodes. Added regression tests intests/test_text_msbf.py. BRSARSoundArchive.to_bytes— sparse & non-contiguousfile_idtable index desynchronization (miorom.platforms.wii.brsar):BRSARSoundArchive.to_bytes()previously compacted the INFO File Table by iterating only oversorted(self._files.keys())and emittinglen(self._files)entries, while writing the raw originals.file_idinto 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_idexceeded the truncated File Table length, causing re-parsed sound entries to receive zero sizes/offsets and shifting file storage keys inself._files, leading to immediateValueError: No file payload founderrors duringexport_wav(). Fixed by structuring the File Table to accommodate all file IDs up tomax_f_idwith 1:1 index-to-file_idcorrespondence, emitting empty(0, 0)descriptors for unreferenced slots, and safely ignoring zero-length entries during re-parsing. Added regression test intests/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 hittingfill_bytewithout 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 withoutfill_bytepadding,_measure_slot()scanned through adjacent strings and data all the way to the end of the ROM. When relinking with a shortened string, the calculatedtailpadded withfill_byteacross 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) addingmax_boundto_measure_slot()constrained by subsequent pointer targets and allocated boundaries; (2) terminating slot measurement on0x00(whenfill_byte != 0); and (3) addingexclude_rangestofind_free_space()to ensure free space runs never overlap pointer table entries or active allocations. Added regression tests intests/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>Ipacking 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). Addedendianparameter ("little","big","<",">") defaulting to Little-Endian forpsx,psp,mips_le, andmips, and Big-Endian forn64andmips_be. Furthermore, implemented complete MIPS branch scanning and rebasing inBranchRelocator.rebase_block()supporting standard conditional branches (BEQ,BNE,BLEZ,BGTZ),REGIMMbranches (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 wherepack_both_u16()andpack_both_u32()usedschema.pack("<HH", val, val)andschema.pack("<II", val, val). Because<HHand<IIformat 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+>Hand<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 invokedMSBFHeaderStruct,MSBFSectionHeaderStruct,FLW3HeaderStruct, andFLW3NodeStruct.from_bytes()and.to_bytes()without passingendian=endian/endian=used_endian. BecauseBinaryStructdefaults 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 hadnode_typebyte-swapped (type1became256), breaking node classification. In.to_bytes(), serializing withendian=">"produced corrupt hybrid binaries with little-endian struct headers and big-endian payloads. Fixed by explicitly threading endianness through allBinaryStructfrom_bytes/to_bytes invocations acrossMSBFFile.OverlayRegion&MemoryOverlayMapper— linear address translation performed on compressed overlays (miorom.core.overlay_mapper):OverlayRegion.ram_to_rom()androm_to_ram()previously ignored the overlayflagsfield (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 returningNoneby default (with optionalraise_on_compressed=TrueraisingCompressedOverlayError) 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 address0x040000D4but was an empty stub (pass), always returning an empty list. Implemented complete ARM and Thumb heuristic scanning usingUniversalDisassemblerto disassemble candidate code windows, perform register state tracking and traceback to literal pool LDRs / immediates, correlate sequential stores to0x040000D4(DMA3SAD),0x040000D8(DMA3DAD), and0x040000DC(DMA3CNT), and construct validDMACopyRecordinstances with accuratepc_address,source_address,destination_address, andword_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-byteb"FLASH_V"with 7-byteb"SRAM_V\x00"instead of 6-byteb"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 trailing0xFE00EOB 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 unhandledValueErrorwhen glyph IDs exceed0x10FFFF.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 frommax_res > 7tomax_res > 3to 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): Packedr_addendas 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 (0xFEFFvs0xFFFE).DolBinary.__init__— immutable bytes input conversion (miorom.link.dol) (BUG-111): Wraps binary input inbytearray()to preventTypeErrorwhen 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 windowrfindupper bound allows forward self-reference (miorom.compression.lz10,miorom.compression.lz11): The lookahead match search previously usedsearch_pos + 3as therfindupper boundary, allowing matches starting ahead ofsearch_posto produce zero or negative displacement backreferences, causing decompressors to fail withCompressionError: invalid displacement. Fixed boundary tosearch_pos + 1and constrained lookahead extension topos + match_len < in_pos.APLib._BitWriter.put_gamma— zero bit emission on unit value (miorom.compression.aplib): Variable-length gamma coding forval <= 1executed 0 loop iterations inwhile temp > 1, emitting 0 bits and corrupting all downstream bitstreams. Added explicit base-case check emitting a single0terminator bit forval <= 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 torun_len < (0x7F + 3)(max 130 bytes). Previously,run_len < (0x7F + 4)allowed runs up to 131 bytes, whererun_len - 3 = 128 = 0x80overflowed the 7-bit length field and zeroed it via0x80 | 0x80, causing decompression to truncate data. Additionally implementedinclude_end_markerbehavior to emitEND_OF_STREAM(0xFF) when requested. Added regression tests for 130, 131, and 262 bytes and randomized roundtrip fuzzing intests/test_rle.py.BinaryLifter.lift— conditional branch CFG fall-through edge omission (miorom.script.lifter):BinaryLifterignoredis_conditional=Trueon 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$) inBinDiffEngine, and broke decompiler control flow. Fixed by emitting both branch-taken and fall-through successor edges forIROp.BRANCH_CONDinstructions, 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 toNOP(), losing operands. AddedIROp.CMPopcode toIROp, 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 despiteis_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 intoIRVar(miorom.script.lifter):convert_to_ssamatched register names usinga.startswith("r"), incorrectly converting expression strings like"r0 == 0"intoIRVar(name="r0 == 0", version=0). Refined register recognition with_is_regto 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 asUINT32whenmax_val > 0xFFFF. Small 32-bit values (such as IDs or counters in0..0xFFFF) previously fell through to 2-byte analysis without advancingcol += 4, permanently desynchronizing all subsequent field offsets. Replaced the simplisticmax_val > 0xFFFFcheck 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 intests/test_struct_profiler.py.StructProfiler.profile_struct— API composability andStrideCandidateauto-unwrapping (miorom.core.struct_profiler):profile_struct()now acceptsUnion[int, StrideCandidate, List[StrideCandidate]], allowing direct pipelining ofautodetect_stride()candidate results intoprofile_struct(data, candidates)without raisingTypeError. Empty lists, invalid types, and non-positive strides now raise clear, descriptiveValueErrorandTypeErrorexceptions.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), causingdecompress()to fail withMalformed Huffman tree: extends beyond input data. Updatedcompress()to emit a valid 7-byte header with a 2-byte tree placeholder (b"\x00\x00"), and updateddecompress()to early-returnb""whenuncompressed_size == 0. Added regression tests intests/test_huffman.py.calc_mips_jump&_disasm_mips— 256MB segment validation and delay-slot base address (miorom.asm.branch,miorom.asm.disasm): Added optionalsrc_addrparameter tocalc_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 inUniversalDisassemblerto 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 inrebase_block()now checksitem.in_rangebefore writing rebased instruction bytes to avoid corrupting out-of-range branches. Added explicit bounds checking toTrampolineHook.create_6502_hook()to ensurecave_ram_addr <= 0xFFFFbefore 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 leftself.fat_offsetand downstream FAT record pointers pointing to pre-shift locations, corrupting FAT tables. Updatedreplace_file()to adjustself.fat_offsetwhen 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 inNARCFileso extracted files retain real filenames instead of generic numbers. PartitionedPSXSaveHeaderStructtitle 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 missingVFSFile.truncate(size=0)method to preventAttributeErrorcrashes when opening existing virtual files in write mode. InIpsPatcher.apply(), guarded trailing IPS32 truncation sizes with0 < 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()soloop_startandloop_endmarkers emit compliant0x94jumps 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 infrom_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 handless8,s16, ands32argument types without silently dropping operands. InBytecodeStreamSplicer.splice_and_shift(), restricted operand repacking andadjusted_countincrements 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): Addedbank_table: Optional[List[int]]parameter toPointerRelinker.scan_pointer_table()to properly map per-entry bank numbers in banked pointer tables. Added two's-complement sign extension toraw_ptrinStringTablePipeline.extract_strings()whenis_relative=True, restoring extraction of backward-relative script pointers.KosinskiCodec.compress— descriptor high-byte truncation (miorom.compression.kosinski): Flushing the final descriptor word omitted writingout[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 withCompressionError. 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 concretePatchHunkrecords filtered expanded ROM bytes usingif byte:, silently omitting all0x00bytes 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 intoraw_val2but never verified the prefix/suffix match(raw_val2 & 0xF800) == 0xF800. Added validation so non-BL 32-bit Thumb instructions starting with0xF000are not corrupted and correctly raiseValueError.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 PowerPCBA/BLAinstructions only provide a 26-bit effective address range, passing physical memory addresses (e.g. GameCube/Wii RAM0x80000000) caused silent truncation via& 0x00FFFFFFto0x00000000. 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 usingsrc_addr & 0xF0000000instead of(src_addr + 4) & 0xF0000000(the delay slot PC). Updated to align withMIPSBranch.decode_j()and the official MIPS architecture specification.apply_outline_1px— missing image buffer size validation (miorom.graphics.pixel_math): Added upfront validation ensuringlen(rgba_bytes) == width * height * 4, preventing unhandledIndexErrorexceptions 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 betweencore.integrityandtext.pipelinewithout 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 duplicateOptionaltyping import inerrors.py. cmd_disasm—UniversalDisassemblercalls undefineddisassemble_stream(miorom.cli.main): Executing themiorom disasmCLI command on non-m68k architectures crashed with anAttributeErrorbecause it calleddisassemble_stream. Updated to call the correctdisassembledescriptor method withbase_addressandmax_instructionsarguments.cmd_scan_text—TranslationRowinstantiated with unexpectedlengthargument (miorom.cli.main): Exporting scanned dialogue strings to a CSV file (miorom scan-text --output) threw aTypeErrordue to an unexpectedlengthkeyword argument being passed toTranslationRow. Removed the invalid argument.ROM Handler—GBRomproperty typocartridge_type_name(miorom.rom.handlers.cartridge): Processing Game Boy ROMs through the universal ROM handler triggered anAttributeErrorbecause it attempted to access.cartridge_type_nameinstead of the actual.cartridge_typeproperty. 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, causingundefined-all-variablelinting errors. Added the missing imports from their respective platform modules. - Missing
NCLRFileTyping Import (miorom.platforms.nds.ncer): Added missingNCLRFileimport to fixF821 undefined namelinting 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 fatalNameErrorexceptions due to missing struct definitions and imports. Added missing imports and definedRLEExtendedSizeStructlocally inrle.pyto restore extended size support.ExtractArchiveStep&PackArchiveStep— mismatched U8Archive classmethod signatures (miorom.pipeline.engine): The pipeline engine incorrectly called non-existentU8Archive.from_bytes()andfrom_directory()methods, crashing during U8 archive tasks. Fixed by using the correctU8Archive.extract_all()andU8Archive.pack()API methods.LiteralRelocator—scan_allmethod missing fromUniversalInstructionScanner(miorom.asm.literal_relocator): The universal fallback path for literal relocation called an undefinedscan_allmethod. Replaced with the correctfind_code_pointersmethod and updated the data argument tocode.table_detector— unreachable code and undefined variables (miorom.scanner.table_detector): Theextract_stringsmethod contained a leftover, unreachablereturn extractedstatement 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 fatalNameErrorexceptions. 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_valueandwrite_pointer_valuepreviously calculated relative targets usingbase_address + raw_valandnew_target_offset - base_address. Fixed to compute relative targets from the pointer's own buffer location (pointer_offset + raw_valandnew_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. Addedprotected_rangessupport to guarantee free-space runs never overlap pointer tables or active allocations.DialogueDissector.extract_from_table— target offset calculation ignorespointer_type(miorom.script.dialogue_dissector): Hardcoded subtraction ofbase_addressbroke relative and banked pointer tables. Addedpointer_typeandbankparameters toextract_from_tableand delegated offset resolution toPointerRelinker._to_rom_offset().DialogueDissector.inject_translations— relocation pointer rewrite uses absolute arithmetic (miorom.script.dialogue_dissector): Relocated string pointers were rewritten usingallocated_off + block.base_addressregardless of addressing mode. Now delegates torelinker._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 forraw_bytespayloads.SmartAutoPaginator.paginate_text— manual newlines collapsed inside paragraphs (miorom.text.paginator): Splitting text with whitespace-collapsing.split()stripped explicit\nlinebreaks 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\x00characters from both search target and candidate names.apply_vertical_gradient— missing buffer validation (miorom.graphics.pixel_math): Added buffer length validation ensuringmask_bytesmatches 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 .donebranched directly to offset0x10wheretable_vaddrwas 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 offset0x18, loading via PC-relativeLDR 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 intoTrampolineHook's inline cave payload caused premature subroutine returns (BX lr/jr $ra; nop) before displaced instructions and trampoline return branches could execute. Addedstandalone: bool = Trueparameter (defaultingFalseinVWFHookEngine.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 ThumbBinstructions 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 raisingRelocationErroron overflow, along withdecode_b()anddecode_bl()decoding helpers.Yaz0.compress— lookback window unbounded byMAX_DIST(miorom.compression.yaz0): Whensearch_depthexceededMAX_DIST(4096 bytes),start_windowlookback calculation allowed match distances greater than 4096, which overflowed the 12-bit distance field and corrupted compressed output. Cappedstart_windowtosrc_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 viaord(ch) & 0xFF, silently altering characters into unrelated ASCII codes. Now raisesValueErrorwhenord(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.validaterelied on simple bracket counts, incorrectly validating reversed (]...[) and nested ([[...]]) brackets; replaced with a bracket stack validator.TagManager.decode_tagspreviously replaced literal\\nwith<ENTER>, destroying escaped strings; made escaped newline conversion optional viaconvert_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. Addedencoding: str = "cp932"support to__init__,from_bytes(), andto_bytes()with automatic fallback.
[1.0.3] — 2026-09-14¶
Fixed¶
ScriptVMDissector.splice_and_relink— branch operand offset off bybase_offset(miorom.script.script_dissector): All branch operand write positions were computed as absolute ROM offsets, butnew_bufis a 0-indexed local script slice. Every branch in a script not at ROM offset 0 was written to the wrong position. Fixed: allop_posvalues now subtractscript.base_offsetbefore indexing intonew_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 torom[:]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): Forhas_length_prefix=Trueinstructions, translations longer than 255 encoded bytes raised a bareValueErrorfrombytearray.append()with no indication of which instruction caused it. Now raises an explicitValueErrorwith the instruction index, ROM offset, and byte count.StringTablePipeline.extract_strings—is_relative=Truepointer arithmetic inverted (miorom.text.pipeline): The formulabase_address + raw_ptrwas used for relative pointers, which is incorrect. A relative pointer is an offset from the pointer's own ROM location; the correct formula isptr_location + raw_ptr. The previous behavior produced garbage file offsets wheneverbase_address != 0, causing silent extraction of wrong bytes and corrupted.pooutput.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 unconditionalbuffer[:len(fixed_data)] = fixed_dataslice left the relocation tail with stale checksum coverage. Emits aRuntimeWarningwhen sizes differ; usesbuffer[:] = fixed_datafor the matching-size path.NestedArchiveVFSclass methodsread/write/list— writes silently discarded (miorom.core.vfs): All three class methods instantiated an emptyNestedArchiveVFS()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 optionaloutput_pathparameter.NestedArchiveVFS— unsupported container format error does not name supported formats (miorom.core.vfs):ParseErroron 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 forIA8(0),RGB565(1), andRGB5A3(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()andTPLImage.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: Addedschemaparameter supporting declarativeSchemaFieldinstances (I16,U16,U32, orBinaryStructsubclasses) to eliminate brittlestructformat strings.ScriptVMDissector.disassemble(): Decodes instruction streams dynamically viaschema.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 viaschema.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; unifiedWordWrapper,FontMetrics, andPixelWordWrapperwithVwfLineWrapper.miorom.text.dte: Consolidated Dual-Tile Encoding; unifiedDTETokenandDTEMinerwithDteOptimizerandDteCodec.miorom.text.tags: Consolidated markup control tags; unifiedTagSyntaxValidatorandTagValidationReportwithTagManager.miorom.asm.branch: Consolidated low-level branch arithmetic; unifiedcalc_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, andresolve_6502_branchdirectly intobranch.py.miorom.asm.xref: Consolidated cross-reference analysis; unifiedXRefDatabase,XRefRecord,XRefDirection, andSymbolicXrefEnginewithGlobalXrefEngine.- 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
structdependencies in dialogue pipelines, script VM relinkers, and user workflows with pure primitives:BinaryReader,BinaryWriter, andBinaryStruct. - 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-byteFNIFblock including pre-calculated pointer fieldspGlyph,pWidth, andpMaprequired byNNS_G2dFontInit(ARM90x02058E54). Legacy implementation generated a 16-byte stub with garbage pointers, causing Data Aborts duringNew Game/ save selection screens. - CMAP multi-block chaining: CMAP blocks (
PAMC) are now segmented by run type and linked vianext_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, andbppfields are accurately serialized from correspondingNFTRFontattributes 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; parsesbaselineandmax_widthfrom PLGC; extractscell_heightfrom FINF.NFTRGlyph: Addedleft_marginattribute 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 withpytest 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-tripto_dict()/from_dict()for JSON layout files.TilemapMenuBox: Rectangular window descriptor enclosing a list ofTilemapTextRunitems; bounding box coordinates are relative to the parent tilemap.TilemapDissector.scan_text_runs(): Scans aTilemaphorizontally or vertically for contiguous runs ofCharMap-mapped tiles; computes trailing blank padding for every run and filters runs shorter thanmin_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 withleft,center, orrightalignment, clears residual tiles topad_tile, and enforcesmax_widthtile 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 discoveredTilemapTextRunlists 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. Providesto_dict()for pipeline logging.VWFHookEngine.verify_hook_site(): Byte-exact integrity guard that compares actual ROM bytes athook_rom_offsetagainstoriginal_instr_bytesbefore any modification; raisesRelocationErroron 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 withlui/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 patchesrom_buffer. Supportssimulate=Truefor 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.tblfiles viaauto_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 usingPNGCodec(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, andTERMINATOR.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; providesget_dialogues()for filtered dialogue extraction andto_po()for direct GNU gettext PO catalog export viaPoHandler.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).
- Text Translation, Far Relinking, Checksum Recalculation & Multi-Format Patching (
-
ARM/Thumb Inline Hook Builder & Code Cave Manager (
miorom.asm.hook_manager): CodeCave: dataclass representing a free-space region withstart,size,used,free, andendproperties.HookRecord: dataclass capturing installed hook metadata:hook_offset,cave_offset,arch,hook_bytes,original_bytes, andhook_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, plusbuild_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 withoffset,bytes_,mnemonic,operands,size,is_branch,is_call,is_return, andbranch_target; exposes.textproperty 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 withcountandendbounds.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; includespatch_single_branch()andrebase_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 includesscan_string_table()for sequential dialogue cluster extraction.- Sub-Byte Bitfield & Packed Record Codec (
miorom.core.bitfield): BitFieldandBitFieldSchema: 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 supportingunpack(),unpack_from(),unpack_all(),pack(),pack_into()for in-place buffer mutation, andpack_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); includesdeduplicate_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
ThumbSnippetmicro-assembly builder (AsmSnippet.thumb()) for GBA & NDS code-caves. - MOS 6502 & W65C816 Architectures (
miorom.asm,miorom.script): - Complete 256-opcode table with dynamic
REP/SEPstatus 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
SnesSnippetbuilder (AsmSnippet.snes()) for SNES routines. - PowerPC 32-bit Code-Cave Tools (
miorom.asm.snippet,miorom.asm.disasm): - Fluent
PpcSnippetbuilder (AsmSnippet.ppc()) for GameCube and Wii (stwu,lwz,stw,addi,li,lis,ori,mr,mflr,mtlr,b,bl,blr). - Added
stwu,lwzu, andor/mrinstruction decoding toUniversalDisassembler._disasm_ppc. - Nintendo Entertainment System Platform (
miorom.platforms.nes): NESRomandNESHeaderStructsupporting 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
UpsPatcherimplementing 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 andAP32container headers. - Registered
yay0andaplibinto unifiedcompress()anddecompress()dispatchers. - Symbolic Cross-References Engine (
miorom.asm.xref_engine): SymbolicXrefEngineandXRefDatabase: 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): CStructOverlayandCStructInstance: 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.fntfiles (both Text and XML formats).- Automatic 2D texture atlas shelf packing for
BitmapFontglyphs. PNGCodec: Built-in pure-Python minimal PNG encoder and decoder (grayscale & RGBA) using only standard libraryzlib.- Sega Genesis 4bpp & SNES Mode 7 Planar 8bpp Tile Formats (
miorom.graphics.tiles): - Added
high_nibble_firstsupport for Sega Mega Drive / Genesis 4bpp chunky tiles and planar 8bpp for SNES Mode 3/4/7 indecode_tile,encode_tile,decode_tileset, andencode_tilesetwith preset platform aliases (genesis,md,snes8,mode7). - GBA Sappy / M4A Sound Engine Scanner & Sample Ripper (
miorom.audio.sappy): - Added
SappyScannerandSappyCodecfor 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
Iso9660Builderfor 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 format0000_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 absoluteJMP $xxxxhooks with automatic displaced instruction execution and return jump. - 24-bit direct
create_snes_hook()synthesizing 4-byte 24-bit far hooks using eitherJML(far jump) orJSL(far subroutine call returning viaRTL). - Integrated into unified
TrampolineHook.create_hook()and binary cave finderTrampolineHook.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 SonyPARAM.SFOmetadata objects supporting UTF-8 string, ASCII string, and uint32 types.PBPFile: Container unpacker and builder for SonyEBOOT.PBParchives 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
PPFPatchersupporting 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.ppfextension 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
refpackandlzssinto unifiedcompress()anddecompress()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_BITMAPSdataset 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, orFontGlyphInjector/FontMetricsinstances.- 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(): ReturnsLineWrapResultwith structuredTextBoxPageobjects, 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 (configurablemin_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, withreserved_codesexclusion andstart_codeassignment control; returnsDteStatswith compression ratio metrics.DteOptimizer.to_tbl()/from_tbl(): Round-trip serialization of DTE token tables to and from standard romhacking.tblcharacter mapping format, preserving trailing spaces within token values.DteCodec.encode()/decode(): High-performance two-way greedy longest-match encoder and decoder integrated withCharMapfor 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 structuredTmLookupResultandTmMatchwith exact (100%) and fuzzy match scoring.pre_fill_po(): Automatically populates untranslated GNU gettext.poentries 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 withCharMapand 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
RelinkReportlogging 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-bitcalculate_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 (0x00or0xFF) and tracking registry for code cave allocation.ArmHookBuilder: Pure bitwise branch instruction synthesizers for ARMBL/B, ThumbBL(long-range 4-byte halfwords), ThumbB(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 withinNDSRomimages; 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.spcformat 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 usingBRRCodec.- 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-16DF4709156BC8A23Ealphabet) producing hyphenatedXXXX-XXXXformat.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 ReplayXXXXXXXX YYYYformat decoder for GBA, N64, and PS1.parse_cheat_code(): Format auto-detector resolving cheat strings toCheatCoderecords 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; providesread_bits(),read_signed_bits(),peek_bits(),skip_bits(), andalign_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()anddecode_sqlite_varint()for 1-9 byte variable-length integers used in embedded database and savegame formats. - Added
zigzag_encode()andzigzag_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()andresolve_arm_branch()for 32-bit B and BL opcodes accounting for 8-byte prefetch pipeline. - Thumb-16:
calc_thumb_branch()andresolve_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()andresolve_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, andcopy_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(): GeneratesPointerSequenceMetricscontaining 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 deduplicatedPointerTableCandidateobjects.- 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(): GeneratesScriptBoundaryReportcovering 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 8x8Tilemapstructures (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_pathinto 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.namesourced from untrusted NARC archive metadata was passed directly toos.path.joinwithout 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, preventingValueError: negative shift countcrashes on corrupted/fuzzed audio. - Systemic Decompression Integrity & Truncation Detection (
miorom.compression): - Added strict payload size validation on
LZ10,LZ11,Yaz0,Yay0,RLE, andHuffman, raisingCompressionErroron unexpected EOF or truncated streams instead of silently corrupting downstream consumers. - Patcher Bounds Validation (
miorom.patch.bps): - Guarded
SourceCopyandTargetCopyinBpsPatcheragainst negative indexing and buffer overflows, raisingPatchError. - DSP-ADPCM Filter Coefficient Bounds (
miorom.audio.dsp_adpcm): - Added coefficient length validation (
len(coefs) >= 16) indecode_frameanddecodeto prevent rawIndexErrorexceptions. - 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
ParseErroron 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 = FalsetoCharMap.encode(), raisingValueErroron unmapped multibyte characters. - Low-RAM Streaming ROM Unpacking for Disc Images (
miorom.rom.handlers): - Added
unpack_filestreaming implementation forGameCubeRomHandlerandIso9660RomHandler, 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) andexport_ghidra_xml(Ghidra Program XML) toXRefDatabase. - Branch Validation Error Handling (
miorom.asm.branch): - Imported
ParseErrorinbranch.pypreventingNameErrorexceptions when validating unaligned branch targets. - ROM Handler Keyword Arguments Passthrough (
miorom.rom.handlers): - Corrected
unpack()forwarding inNDSRomHandler,GameCubeRomHandler, andIso9660RomHandlerto prevent duplicatefilepathargument errors when delegating tounpack_file(). - Streaming ROM Handler Filepath Probing (
miorom.rom.handlers): - Enhanced
can_handleinIso9660RomHandlerandGameCubeRomHandlerto probe magic headers directly fromfilepathwhen 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 acceptsUnion[bytes, bytearray], automatically casting immutablebytesto a mutablebytearraywhen needed.- Documentation Design with MkDocs Material:
- Added documentations.
[0.13.0] — 2026-09-10¶
Added¶
- DMA Filesystem Abstraction (
miorom.archive.dma): - Declarative
DmaTableEntryStructviaBinaryStruct. DmaTableArchiveandDmaFileEntryfor 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) toMipsSnippet. - 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, andCI4. - 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]\ntextformat 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): SegmentTableandSegmentedAddressResolverfor 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) andN64Audiobank(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), andto_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 pairedR_MIPS_HI16andR_MIPS_LO16relocations.- 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 structuredParseErrorexceptions.- Emulator Database Compatibility (
miorom.platforms.n64): - Added
preserve_database_crcparameter toN64Rom.recalculate_checksum()andfix_n64_checksum()to retain No-Intro database matching for emulator boxart and profiles.
Changed¶
- Standardized
CsvHandler.export_csv()andexport_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.