From 860bd6a237bf122077927a7ea8f4bf3a43f64411 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 11 Sep 2026 22:56:59 -0700 Subject: [PATCH] Add verified two-bit tagged MIPS relocation reader --- tools/mips_tagged_relocations.py | 75 +++++++++++++++++++++ tools/tests/test_mips_tagged_relocations.py | 47 +++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 tools/mips_tagged_relocations.py create mode 100644 tools/tests/test_mips_tagged_relocations.py diff --git a/tools/mips_tagged_relocations.py b/tools/mips_tagged_relocations.py new file mode 100644 index 00000000..cb90919f --- /dev/null +++ b/tools/mips_tagged_relocations.py @@ -0,0 +1,75 @@ +"""Reconstruct MIPS images with a trailing two-bit tagged relocation stream. + +The first little-endian word is the byte offset of the stream and therefore +the retained image size. Stream words encode an aligned image offset plus: +0 = ABS32, 1 = HI16 with a following full addend, 2 = LO16, 3 = J26. +0xffffffff ends the stream. This format carries no load address: callers must +establish each destination independently from the original loader. +""" +from dataclasses import dataclass +import struct + + +@dataclass(frozen=True) +class Relocation: + offset: int + kind: int + addend: int | None = None + + +def parse(data: bytes) -> tuple[bytes, tuple[Relocation, ...]]: + """Validate the entire file and return its retained image and relocations.""" + if len(data) < 8 or len(data) % 4: + raise ValueError("Relocation file must contain aligned header and terminator") + end = struct.unpack_from(" len(data) - 4: + raise ValueError("Invalid relocation stream offset") + cursor, relocations, written = end, [], set() + while cursor <= len(data) - 4: + tag = struct.unpack_from(" end: + raise ValueError("Relocation target outside retained image") + if offset in written: + raise ValueError("Repeated relocation target") + written.add(offset) + if kind == 1: + if cursor > len(data) - 4: + raise ValueError("Truncated HI16 addend") + addend = struct.unpack_from(" tuple[bytes, tuple[Relocation, ...]]: + """Apply the loader's 32-bit and halfword writes at a verified RAM base.""" + source, relocations = parse(data) + if load_addr % 4 or not 0x80000000 <= load_addr < load_addr + len(source) <= 0x80200000: + raise ValueError("Relocated image must fit aligned PlayStation main RAM") + image = bytearray(source) + for item in relocations: + offset = item.offset + if item.kind == 0: + word = struct.unpack_from("> 16 + struct.pack_into("> 6) + struct.pack_into("