ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
169 lines
6.4 KiB
Python
169 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Convert a PS2 EE ELF into a tb_ee_core_elf_runner-compatible image
|
|
manifest pair (no external deps; pure stdlib).
|
|
|
|
Emits two files at the requested output prefix, identical in format
|
|
to generate_synthetic_image.py:
|
|
|
|
<prefix>.image.hex iverilog $readmemh, @<qw_idx> directives for
|
|
populated 128-bit qwords only. Each line is
|
|
32 hex chars (MSB-first, byte 15 leftmost).
|
|
|
|
<prefix>.manifest.hex line 0 = ELF entry point (32-bit hex)
|
|
line 1 = stack-top hint (32-bit hex)
|
|
|
|
Supports ELF32 little-endian, ELFCLASS32, EM_MIPS, e_type ET_EXEC or
|
|
ET_DYN. PT_LOAD segments are placed at their physical address
|
|
(low 29 bits of p_vaddr — strips the kuseg/kseg0/kseg1 alias bits so
|
|
the data lands at the correct phys offset in ee_ram_stub).
|
|
|
|
Stack-top is approximated as (ee_ram_bytes - 0x10) since real PS2
|
|
ELFs don't carry a stack pointer in their headers; the TB will set
|
|
$sp to this if the manifest is read.
|
|
|
|
Usage:
|
|
elf_to_eeram.py --in path/to/game.elf --out-prefix /tmp/game
|
|
|
|
Verdict-aware notes:
|
|
* Segments overflowing the EE RAM image cause a fatal error.
|
|
* Segments overlapping each other are flagged but not fatal — the
|
|
later one wins (matches how a real loader would behave).
|
|
"""
|
|
|
|
import sys
|
|
import struct
|
|
import argparse
|
|
|
|
|
|
ELFMAG = b"\x7fELF"
|
|
ELFCLASS32 = 1
|
|
ELFDATA2LSB = 1
|
|
EM_MIPS = 8
|
|
ET_EXEC = 2
|
|
ET_DYN = 3
|
|
PT_LOAD = 1
|
|
|
|
|
|
def parse_elf32_le(data: bytes):
|
|
"""Return (entry, [(p_vaddr, p_offset, p_filesz, p_memsz), ...])
|
|
for PT_LOAD segments of a 32-bit little-endian MIPS ELF.
|
|
Raises ValueError on bad magic / wrong class / wrong arch.
|
|
"""
|
|
if len(data) < 52 or data[:4] != ELFMAG:
|
|
raise ValueError("not an ELF file (bad magic)")
|
|
if data[4] != ELFCLASS32:
|
|
raise ValueError(f"only ELFCLASS32 supported (got class={data[4]})")
|
|
if data[5] != ELFDATA2LSB:
|
|
raise ValueError(f"only little-endian supported (got data={data[5]})")
|
|
|
|
(e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags,
|
|
e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx) = \
|
|
struct.unpack_from("<HHIIIIIHHHHHH", data, 16)
|
|
|
|
if e_machine != EM_MIPS:
|
|
raise ValueError(f"only EM_MIPS supported (got machine={e_machine})")
|
|
if e_type not in (ET_EXEC, ET_DYN):
|
|
raise ValueError(f"only ET_EXEC / ET_DYN supported (got type={e_type})")
|
|
|
|
pt_load = []
|
|
for i in range(e_phnum):
|
|
off = e_phoff + i * e_phentsize
|
|
(p_type, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz,
|
|
p_flags, p_align) = struct.unpack_from("<IIIIIIII", data, off)
|
|
if p_type == PT_LOAD:
|
|
pt_load.append((p_vaddr, p_offset, p_filesz, p_memsz))
|
|
|
|
return e_entry, pt_load
|
|
|
|
|
|
def build_image(elf_bytes: bytes, ee_ram_bytes: int):
|
|
entry, segs = parse_elf32_le(elf_bytes)
|
|
image = bytearray(ee_ram_bytes)
|
|
|
|
placed = []
|
|
for (p_vaddr, p_offset, p_filesz, p_memsz) in segs:
|
|
phys = p_vaddr & 0x1FFFFFFF # strip kseg/kuseg bits
|
|
if phys + p_memsz > ee_ram_bytes:
|
|
raise ValueError(
|
|
f"PT_LOAD at vaddr=0x{p_vaddr:08x} phys=0x{phys:08x} "
|
|
f"size=0x{p_memsz:x} overflows EE RAM (0x{ee_ram_bytes:x})")
|
|
# Detect overlap (informational only).
|
|
for (lo, hi) in placed:
|
|
if not (phys + p_memsz <= lo or phys >= hi):
|
|
print(f"[elf_to_eeram] WARNING: PT_LOAD at phys=0x{phys:08x} "
|
|
f"size=0x{p_memsz:x} overlaps prior placement",
|
|
file=sys.stderr)
|
|
placed.append((phys, phys + p_memsz))
|
|
|
|
# Copy p_filesz bytes from file at p_offset → phys. p_memsz can
|
|
# be larger than p_filesz (.bss tail); image is already zero-
|
|
# initialised so the tail is naturally zero.
|
|
chunk = elf_bytes[p_offset:p_offset + p_filesz]
|
|
image[phys:phys + p_filesz] = chunk
|
|
print(f"[elf_to_eeram] placed PT_LOAD vaddr=0x{p_vaddr:08x} "
|
|
f"phys=0x{phys:08x} filesz=0x{p_filesz:x} memsz=0x{p_memsz:x}")
|
|
|
|
return entry, image
|
|
|
|
|
|
def qword_to_hex(image: bytearray, qw_phys: int) -> str:
|
|
"""MSB-first hex string for the qword at byte offset qw_phys."""
|
|
bytes16 = image[qw_phys:qw_phys + 16]
|
|
return bytes16[::-1].hex()
|
|
|
|
|
|
def emit_image_hex(image: bytearray, path: str) -> None:
|
|
qw_size = 16
|
|
with open(path, "w") as f:
|
|
f.write("// Ch270 ELF-derived EE-RAM image\n")
|
|
f.write(f"// {len(image)} bytes / {len(image)//qw_size} qwords\n")
|
|
f.write("// Populated qwords only; TB zero-inits before $readmemh.\n\n")
|
|
any_emitted = False
|
|
for qw_idx in range(0, len(image) // qw_size):
|
|
qw_byte = qw_idx * qw_size
|
|
qw_bytes = image[qw_byte:qw_byte + qw_size]
|
|
if any(b != 0 for b in qw_bytes):
|
|
f.write(f"@{qw_idx:08x}\n")
|
|
f.write(qword_to_hex(image, qw_byte) + "\n")
|
|
any_emitted = True
|
|
if not any_emitted:
|
|
f.write("@00000000\n00000000000000000000000000000000\n")
|
|
|
|
|
|
def emit_manifest_hex(path: str, entry: int, stack_top: int) -> None:
|
|
with open(path, "w") as f:
|
|
f.write("// Ch270 manifest from ELF\n")
|
|
f.write(f"// line 0 = entry, line 1 = stack_top hint\n")
|
|
f.write(f"{entry:08x}\n")
|
|
f.write(f"{stack_top:08x}\n")
|
|
|
|
|
|
def main() -> int:
|
|
p = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
p.add_argument("--in", dest="elf_in", required=True,
|
|
help="input ELF path")
|
|
p.add_argument("--out-prefix", required=True,
|
|
help="output file prefix")
|
|
p.add_argument("--ee-ram-bytes", type=lambda s: int(s, 0),
|
|
default=2 * 1024 * 1024,
|
|
help="EE RAM size in bytes (default 2 MiB)")
|
|
args = p.parse_args()
|
|
|
|
with open(args.elf_in, "rb") as f:
|
|
elf_bytes = f.read()
|
|
|
|
entry, image = build_image(elf_bytes, args.ee_ram_bytes)
|
|
stack_top = args.ee_ram_bytes - 0x10
|
|
emit_image_hex(image, f"{args.out_prefix}.image.hex")
|
|
emit_manifest_hex(f"{args.out_prefix}.manifest.hex", entry, stack_top)
|
|
print(f"[elf_to_eeram] wrote {args.out_prefix}.image.hex + "
|
|
f"{args.out_prefix}.manifest.hex (entry=0x{entry:08x}, "
|
|
f"stack_top=0x{stack_top:08x}, ee_ram={args.ee_ram_bytes} bytes)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|