Fetch and verify the four MAME indy_4610 PROM dumps, wrap the newest dump in an ELF32 BE MIPS image for Ghidra/REA, and record the resulting structural map: image layout, exception vector table, boot flow, function anchors and firmware feature clusters.
116 lines
2.9 KiB
Python
Executable File
116 lines
2.9 KiB
Python
Executable File
#!/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())
|