Resolve call-script dispatch: id = raw SYS4INI file index
Native-RE (Ghidra) cracked call-script <id> (opcode 0x03): its handler FUN_0041bc90 -> loader FUN_0040e980 -> resolver FUN_0044f390 indexes an 80-byte record table at base + id*0x50 == the SYS4INI record layout. So `call-script <id>` is a direct RAW index into the SYS4INI global file table (the asset index we already parse) -- there is no separate on-disk id->code registry. This resolves name-resolution.md #1, statically, no Frida. Confirmed: all 297 distinct corpus call-script ids resolve to a .BIN script with a semantically-exact name (0x1ab->ADDITEM, 0x2ae7->MES, 0x143->BUNKI, 0x329d->CALCREVISE), 0 out-of-range, 0 alternate-pack. Companion op 0x8f `call` is an intra-script JSR (FUN_0041fba0), not cross-script. - parse_sys4ini.py: preserve `raw_index` per entry (= the engine file id; index the RAW records incl. '@' placeholders) + emit build/callscript-names.json (id->name). - sys4load.py: annotate `call-script 0x1ab =ADDITEM.BIN`. - opcodes.toml 0x03/0x8f refined (source=investigation, confidence high, handler VAs) + rebuilt opcode-reference.md. - docs: engine-re.md (op 0x03 section + backlog re-aimed), name-resolution.md #1 (SOLVED), script-inventory.md (call graph + living-reference decision), tools-reference.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -115,13 +115,18 @@ def parse(path: Path) -> dict:
|
||||
f"have {len(blob)} decompressed bytes)")
|
||||
|
||||
files = []
|
||||
for _ in range(file_count):
|
||||
for i in range(file_count):
|
||||
name_b, arc_id, file_number, offset, size = struct.unpack_from(FILE_ENTRY_FMT, blob, p)
|
||||
p += FILE_ENTRY_LEN
|
||||
name = _cstr(name_b)
|
||||
if name == "@" or not name:
|
||||
continue
|
||||
files.append({
|
||||
# raw_index = the record's 0-based position in the SYS4INI table INCLUDING '@'
|
||||
# placeholders. This is the engine's universal file id: `call-script <id>` and every
|
||||
# script/asset load index by it (engine FUN_0044f390: record = base + id*0x50). It is
|
||||
# NOT the same as this entry's position in `files` (which omits placeholders).
|
||||
"raw_index": i,
|
||||
"name": name,
|
||||
"archive": archives[arc_id] if 0 <= arc_id < arc_count else None,
|
||||
"arc_id": arc_id,
|
||||
@@ -215,6 +220,15 @@ def main() -> int:
|
||||
out.write_text(json.dumps(index, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(f"-> {out.relative_to(paths.REPO)}")
|
||||
|
||||
# call-script id -> name map (raw_index keyed). `call-script <id>` (opcode 0x03) is a direct
|
||||
# raw index into the SYS4INI file table, so this IS the call-graph name registry that
|
||||
# name-resolution.md #1 needed (confirmed via native-RE, see docs/engine-re.md). sys4load reads
|
||||
# it to annotate `call-script 0x1ab =ADDITEM`.
|
||||
cs = {str(f["raw_index"]): f["name"] for f in index["files"]}
|
||||
cs_out = paths.BUILD / "callscript-names.json"
|
||||
cs_out.write_text(json.dumps(cs, ensure_ascii=False, indent=0), encoding="utf-8")
|
||||
print(f"-> {cs_out.relative_to(paths.REPO)} ({len(cs)} ids)")
|
||||
|
||||
if do_check:
|
||||
print("--- validation ---")
|
||||
return 1 if check(index) else 0
|
||||
|
||||
Reference in New Issue
Block a user