Reverse-engineering + open reimplementation workspace for Eushully's AGE/SYS4 engine (first target: Himegari). The repo root is age-reimpl/; the original game install and the extracted ALF data are siblings outside the repo and are never tracked. build/ (derived corpora) is gitignored and regenerated by the tools. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
2.9 KiB
Python
77 lines
2.9 KiB
Python
"""Verify the three (count, offset) table descriptors and inspect table contents.
|
|
|
|
Layout hypothesis (all offsets in dwords, relative to body start at 0x3C):
|
|
code: body[0 .. F8)
|
|
T1: count=F7, offset=F8, entry size s1 (solve: F8 + s1*F7 == F10)
|
|
T2: count=F9, offset=F10, entry size s2 (solve: F10 + s2*F9 == F12)
|
|
T3: count=F11, offset=F12, entry size s3 (solve: F12 + s3*F11 == EOF)
|
|
"""
|
|
import struct
|
|
from pathlib import Path
|
|
from collections import Counter
|
|
|
|
ROOT = Path(__file__).resolve().parents[2] / "extracted" / "DATA1"
|
|
|
|
def parse(path):
|
|
data = path.read_bytes()
|
|
f = struct.unpack_from("<13I", data, 8)
|
|
return data, f
|
|
|
|
def solve_entry_size(gap_pairs):
|
|
"""gap_pairs: list of (count, gap_dwords). Return consistent entry size or None."""
|
|
sizes = set()
|
|
for count, gap in gap_pairs:
|
|
if count:
|
|
if gap % count:
|
|
return f"non-integer ({count},{gap})"
|
|
sizes.add(gap // count)
|
|
return sizes
|
|
|
|
def main():
|
|
files = sorted(ROOT.glob("*.BIN"))
|
|
t1_pairs, t2_pairs, t3_pairs = [], [], []
|
|
bad = []
|
|
for p in files:
|
|
data, f = parse(p)
|
|
nd = len(data) // 4 - 15 # body dwords
|
|
F7, F8, F9, F10, F11, F12 = f[7], f[8], f[9], f[10], f[11], f[12]
|
|
if not (F8 <= F10 <= F12 <= nd):
|
|
bad.append((p.name, F8, F10, F12, nd))
|
|
continue
|
|
t1_pairs.append((F7, F10 - F8))
|
|
t2_pairs.append((F9, F12 - F10))
|
|
t3_pairs.append((F11, nd - F12))
|
|
print(f"ordering violations: {len(bad)} {bad[:5]}")
|
|
print("T1 entry sizes:", solve_entry_size(t1_pairs))
|
|
print("T2 entry sizes:", solve_entry_size(t2_pairs))
|
|
print("T3 entry sizes:", solve_entry_size(t3_pairs))
|
|
|
|
# zero-count but nonzero-gap sanity
|
|
for name, pairs in (("T1", t1_pairs), ("T2", t2_pairs), ("T3", t3_pairs)):
|
|
odd = sum(1 for c, g in pairs if c == 0 and g != 0)
|
|
print(f"{name}: count==0 but gap!=0 in {odd} files")
|
|
|
|
# F2 outliers
|
|
print("\nF2 outliers:")
|
|
for p in files:
|
|
_, f = parse(p)
|
|
if f[2] not in (1, 2):
|
|
print(f" {p.name}: F2={f[2]:#x}")
|
|
|
|
# dump tables for a few files
|
|
for name in ("MENU.BIN", "ADDEXP.BIN", "ADDILL.BIN", "ALCHEMY.BIN"):
|
|
data, f = parse(ROOT / name)
|
|
nd = len(data) // 4 - 15
|
|
print(f"\n=== {name}: body={nd} F7-12: cnt/off T1={f[7]}/{f[8]} "
|
|
f"T2={f[9]}/{f[10]} T3={f[11]}/{f[12]}")
|
|
for label, cnt, off, end in (("T1", f[7], f[8], f[10]),
|
|
("T2", f[9], f[10], f[12]),
|
|
("T3", f[11], f[12], nd)):
|
|
vals = struct.unpack_from(f"<{end-off}I", data, 0x3C + off*4)
|
|
show = ", ".join(f"{v:#x}" for v in vals[:16])
|
|
print(f" {label} ({cnt} entries, {end-off} dwords): {show}"
|
|
+ (" ..." if end-off > 16 else ""))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|