Track the 410console hand-off docs; ignore the raw Mac archive
The BattleTech port spec, the Console 4.10 decompilation notes, the golden reference eggs, and the PEF/rsrc analysis tools come into the repo. The .sit archive and its extraction (Mac binaries, __MACOSX/.finf metadata) stay untracked for now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ppcxref.py -- TOC-aware PPC annotator for the Console 4.10 PEF.
|
||||
|
||||
Resolves r2(TOC)-relative loads to data addresses and, when those point at
|
||||
C strings, annotates the disassembly. Also builds a string cross-reference:
|
||||
which code addresses reference which strings.
|
||||
|
||||
r2 (TOC base) = data section offset 0x8000 (from the main transition vector).
|
||||
|
||||
Usage:
|
||||
py -3.13 ppcxref.py annotate <start> <count> # annotated disasm
|
||||
py -3.13 ppcxref.py xref <substring> # find code refs to strings matching substring
|
||||
py -3.13 ppcxref.py toc <hexoff> # what does TOC[-off(r2)] point to
|
||||
"""
|
||||
import sys, struct, capstone
|
||||
sys.path.insert(0, __import__('os').path.dirname(__file__))
|
||||
from pefparse import PEF
|
||||
|
||||
BIN = r"C:\VWE\TeslaRel410\410console\4_10-console-extracted\Console 4.10"
|
||||
TOC_BASE = 0x8000 # r2 value = data section offset
|
||||
|
||||
pef = PEF(open(BIN,'rb').read())
|
||||
CODE = pef.sections[0].data()
|
||||
DATA = pef.sections[1].data()
|
||||
|
||||
def d32(off):
|
||||
if off < 0 or off+4 > len(DATA): return None
|
||||
return struct.unpack_from('>I', DATA, off)[0]
|
||||
|
||||
def cstr_at(off, maxlen=80):
|
||||
if off < 0 or off >= len(DATA): return None
|
||||
e = off
|
||||
while e < len(DATA) and DATA[e] != 0 and (e-off) < maxlen:
|
||||
c = DATA[e]
|
||||
if c < 0x09 or (c > 0x0d and c < 0x20) or c > 0x7e:
|
||||
# allow only printable; stop otherwise
|
||||
if c != 0xc9: # ellipsis
|
||||
break
|
||||
e += 1
|
||||
s = DATA[off:e]
|
||||
try:
|
||||
txt = s.decode('mac_roman')
|
||||
except Exception:
|
||||
return None
|
||||
# require mostly printable and length>=3
|
||||
printable = sum(1 for c in s if 0x20 <= c <= 0x7e)
|
||||
if len(s) >= 3 and printable >= max(3, int(len(s)*0.8)):
|
||||
return txt
|
||||
return None
|
||||
|
||||
def toc_target(disp):
|
||||
"""disp is signed offset from r2. Returns (data_off, pointed_value, string_or_None)."""
|
||||
off = TOC_BASE + disp
|
||||
val = d32(off)
|
||||
s = None
|
||||
if val is not None:
|
||||
s = cstr_at(val)
|
||||
return off, val, s
|
||||
|
||||
md = capstone.Cs(capstone.CS_ARCH_PPC, capstone.CS_MODE_32 | capstone.CS_MODE_BIG_ENDIAN)
|
||||
md.detail = True
|
||||
|
||||
def annotate(start, count):
|
||||
base = 0
|
||||
for insn in md.disasm(CODE[start:start+count*4], base+start):
|
||||
note = ''
|
||||
# detect r2-relative: op_str contains "r2)" with a displacement
|
||||
ops = insn.op_str
|
||||
# capstone PPC prints e.g. "r4, -0x6c9c(r2)" or "r3, r2, -0x6848"
|
||||
disp = None
|
||||
if '(r2)' in ops:
|
||||
# form: rX, disp(r2)
|
||||
try:
|
||||
d = ops.split(',')[-1].strip()
|
||||
d = d[:d.index('(r2)')]
|
||||
disp = int(d, 16) if d.startswith(('0x','-0x')) else int(d)
|
||||
except Exception:
|
||||
disp = None
|
||||
if disp is not None:
|
||||
o, val, s = toc_target(disp)
|
||||
if val is not None:
|
||||
note = "; TOC[0x%x]=*0x%x=0x%x" % (o, o, val)
|
||||
if s: note += ' "%s"' % s
|
||||
elif 'r2,' in ops and insn.mnemonic in ('addi','addic','subi'):
|
||||
# form: rX, r2, disp -> address = TOC_BASE+disp (pointer to data directly)
|
||||
try:
|
||||
d = ops.split(',')[-1].strip()
|
||||
disp = int(d, 16) if d.startswith(('0x','-0x')) else int(d)
|
||||
except Exception:
|
||||
disp = None
|
||||
if disp is not None:
|
||||
addr = TOC_BASE + disp
|
||||
s = cstr_at(addr)
|
||||
note = "; &data[0x%x]" % addr
|
||||
if s: note += ' "%s"' % s
|
||||
elif insn.mnemonic == 'lis':
|
||||
# high-half load, often followed by addi to form an absolute data ptr
|
||||
note = ''
|
||||
print("0x%08x %-8s %-28s %s" % (insn.address, insn.mnemonic, insn.op_str, note))
|
||||
|
||||
def build_string_table():
|
||||
"""All C strings in data with >=4 printable chars -> {addr: text}."""
|
||||
tbl = {}
|
||||
i = 0
|
||||
n = len(DATA)
|
||||
while i < n:
|
||||
if 0x20 <= DATA[i] <= 0x7e:
|
||||
j = i
|
||||
while j < n and (0x20 <= DATA[j] <= 0x7e):
|
||||
j += 1
|
||||
if j - i >= 4:
|
||||
tbl[i] = DATA[i:j].decode('ascii','replace')
|
||||
i = j
|
||||
else:
|
||||
i += 1
|
||||
return tbl
|
||||
|
||||
def xref(substr):
|
||||
# find data offsets of strings containing substr
|
||||
strtbl = build_string_table()
|
||||
targets = {a:t for a,t in strtbl.items() if substr.lower() in t.lower()}
|
||||
if not targets:
|
||||
print("no strings match", substr); return
|
||||
print("=== target strings ===")
|
||||
for a,t in sorted(targets.items()):
|
||||
print(" 0x%06x %s" % (a, t))
|
||||
# Build TOC reverse map: which TOC slots hold pointers into these strings
|
||||
# (pointer may target start OR interior). Map value->slot.
|
||||
tocslots = {}
|
||||
for slot in range(0, len(DATA), 4):
|
||||
v = d32(slot)
|
||||
if v in targets:
|
||||
tocslots.setdefault(v, []).append(slot)
|
||||
# Also direct addi r2 references use TOC_BASE+disp == string addr directly
|
||||
print("\n=== code references ===")
|
||||
# Disassemble whole code, look for r2-relative producing an address in targets
|
||||
for insn in md.disasm(CODE, 0):
|
||||
ops = insn.op_str
|
||||
hit = None
|
||||
if '(r2)' in ops:
|
||||
try:
|
||||
d = ops.split(',')[-1].strip(); d = d[:d.index('(r2)')]
|
||||
disp = int(d,16) if d.startswith(('0x','-0x')) else int(d)
|
||||
o = TOC_BASE+disp; v = d32(o)
|
||||
if v in targets: hit = (v, targets[v], 'TOC')
|
||||
except Exception: pass
|
||||
elif 'r2,' in ops and insn.mnemonic in ('addi','subi'):
|
||||
try:
|
||||
d = ops.split(',')[-1].strip()
|
||||
disp = int(d,16) if d.startswith(('0x','-0x')) else int(d)
|
||||
addr = TOC_BASE+disp
|
||||
if addr in targets: hit = (addr, targets[addr], 'direct')
|
||||
except Exception: pass
|
||||
if hit:
|
||||
print(" 0x%08x %-8s %-24s -> %s \"%s\"" % (insn.address, insn.mnemonic, ops, hit[2], hit[1]))
|
||||
|
||||
if __name__ == '__main__':
|
||||
cmd = sys.argv[1]
|
||||
if cmd == 'annotate':
|
||||
annotate(int(sys.argv[2],0), int(sys.argv[3],0))
|
||||
elif cmd == 'xref':
|
||||
xref(sys.argv[2])
|
||||
elif cmd == 'toc':
|
||||
o,v,s = toc_target(int(sys.argv[2],0))
|
||||
print("TOC off=0x%x val=0x%x str=%s" % (o, v if v else 0, s))
|
||||
Reference in New Issue
Block a user