#!/usr/bin/env python3 """Wrap a raw SGI Indy IP24 PROM dump in a minimal ELF32 big-endian MIPS image. REA's Ghidra MIPS lane accepts ELF32 ET_EXEC images, but not headerless MIPS binaries. This tool produces a load-image-only wrapper so the PROM can be opened at its real physical address (0x1FC00000) without modifying the ROM bytes. The wrapper deliberately declares EF_MIPS_ARCH_32 with an unspecified ABI and provides no PT_MIPS_ABIFLAGS record. That selects REA's "recordless legacy" admission, which pins Ghidra to MIPS:BE:32:default, a suitable match for the R4000/R4600-era Indy PROM. """ from __future__ import annotations import argparse import os import struct import sys ROM_BASE = 0x1FC00000 PAGE_SIZE = 0x1000 EM_MIPS = 8 ET_EXEC = 2 EF_MIPS_ARCH_32 = 0x50000000 EI_NIDENT = 16 def build_elf(rom: bytes, base: int, entry: int) -> bytes: payload_offset = PAGE_SIZE e_ident = bytearray(EI_NIDENT) e_ident[0:4] = b"\x7fELF" e_ident[4] = 1 # ELFCLASS32 e_ident[5] = 2 # ELFDATA2MSB e_ident[6] = 1 # EV_CURRENT e_ident[7] = 0 # ELFOSABI_NONE e_entry = entry e_phoff = 52 e_shoff = 0 e_flags = EF_MIPS_ARCH_32 e_ehsize = 52 e_phentsize = 32 e_phnum = 1 e_shentsize = 0 e_shnum = 0 e_shstrndx = 0 ehdr = struct.pack( ">16sHHIIIIIHHHHHH", bytes(e_ident), ET_EXEC, EM_MIPS, 1, e_entry, e_phoff, e_shoff, e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx, ) # PT_LOAD: file offset 0x1000 -> virtual address base, whole ROM. phdr = struct.pack( ">IIIIIIII", 1, # PT_LOAD payload_offset, base, base, len(rom), len(rom), 5, # PF_R | PF_X PAGE_SIZE, ) header = ehdr + phdr assert len(header) <= payload_offset return header.ljust(payload_offset, b"\x00") + rom def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("rom", help="raw PROM binary (512 KiB)") parser.add_argument("-o", "--output", help="output ELF path") parser.add_argument("--base", type=lambda s: int(s, 0), default=ROM_BASE) parser.add_argument("--entry", type=lambda s: int(s, 0), default=None) args = parser.parse_args() with open(args.rom, "rb") as fh: rom = fh.read() if len(rom) != 0x80000: print( f"warning: {args.rom} is {len(rom)} bytes, expected 524288", file=sys.stderr ) entry = args.base if args.entry is None else args.entry out = args.output if out is None: stem, _ = os.path.splitext(args.rom) out = stem + ".elf" with open(out, "wb") as fh: fh.write(build_elf(rom, args.base, entry)) print(out) return 0 if __name__ == "__main__": raise SystemExit(main())