Nothing here changes what a tool computes; it changes where tools look. - tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has existed nowhere since /work became a clone, so they matched nothing and printed empty results. They now resolve the disc through a new disc.py from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised). Nine scripts that imported siblings from the retired Reborn checkout or an old session scratchpad now import from their own directory. unitgroup.py only needs the variable when --pak is not given. - sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead devkit key and a doc comment claiming a devkit fallback that does not exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either. - sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so always skipped. It reads $SYLPHEED_DISC now, and passes against the disc. - Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe as places to look now name sylpheed.db, Canary's ppc_context.h and the flat .pe; docs/re/README.md no longer says the native Canary build does not run. Historical records keep their original paths: findings that were measured against /work/xenia-rs/sylpheed.db still say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
75 lines
3.4 KiB
Python
75 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Dump any `stage\\*.tbl` mission table from GP_MAIN_GAME_<lang>.pak.
|
|
|
|
Pure static work -- reads the extracted disc, runs no emulator. The container is
|
|
the same self-describing IDXD format decoded for the squadron roster; see
|
|
docs/re/structures/unit-group-table.md and tools/re-capture/unitgroup.py.
|
|
|
|
python3 tools/re-capture/stagetbl.py Stage_S02 # by stage record
|
|
python3 tools/re-capture/stagetbl.py --name 'stage\\AIParams_S02.tbl'
|
|
python3 tools/re-capture/stagetbl.py --follow S02 # stage record + all
|
|
# tables it names
|
|
"""
|
|
import argparse, re, sys
|
|
from disc import disc_root
|
|
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
|
from unitgroup import read_entry, name_hash, parse, named
|
|
|
|
PAK = disc_root() + '/dat/GP_MAIN_GAME_E.pak'
|
|
|
|
def stage_record_hash(stage):
|
|
"""The per-stage definition record is not name-addressed; find it by content."""
|
|
import struct, glob, os, zlib
|
|
idx = open(PAK, 'rb').read()
|
|
base = PAK[:-4]
|
|
data = b''.join(open(p, 'rb').read() for p in sorted(glob.glob(base + '.p[0-9][0-9]')))
|
|
want = ('UnitGroup_%s.tbl' % stage).encode() # the _Test template names
|
|
# Stage_S02.xpr too, so key
|
|
# off the squadron table
|
|
for i in range(struct.unpack_from('>I', idx, 4)[0]):
|
|
h, off, cs = struct.unpack_from('>III', idx, 16 + i * 12)
|
|
s = data[off:off + cs]
|
|
try: b = zlib.decompress(s[10:]) if s[:2] == b'Z1' else s
|
|
except Exception: continue
|
|
if b[:4] == b'IDXD' and want in b and b'EnumerateSquadron' in b:
|
|
return h
|
|
raise KeyError('no stage record for ' + stage)
|
|
|
|
def dump(label, recs, limit=None):
|
|
print('=== %s (%d records) ===' % (label, len(recs)))
|
|
for r in recs[:limit]:
|
|
print('-- %r (%d fields)' % (r['squadron'], len(r['fields'])))
|
|
for tag, fn, v in r['fields']:
|
|
print(' %08x %-30s = %s' % (tag, fn if fn else '(pos)', v))
|
|
if limit and len(recs) > limit:
|
|
print(' ... %d more records not shown' % (len(recs) - limit))
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('target', nargs='?', default='Stage_S02')
|
|
ap.add_argument('--name', help=r'archive name, e.g. stage\AIParams_S02.tbl')
|
|
ap.add_argument('--follow', metavar='SNN', help='stage record plus every .tbl it names')
|
|
ap.add_argument('--limit', type=int, default=None)
|
|
a = ap.parse_args()
|
|
if a.name:
|
|
dump(a.name, parse(read_entry(PAK, name_hash(a.name))), a.limit); return 0
|
|
stage = a.follow or (a.target[6:] if a.target.startswith('Stage_') else a.target)
|
|
recs = parse(read_entry(PAK, stage_record_hash(stage)))
|
|
dump('Stage_%s definition record' % stage, recs)
|
|
if not a.follow: return 0
|
|
seen = set()
|
|
for r in recs:
|
|
for _, fn, v in r['fields']:
|
|
if not v.endswith('.tbl') or v in seen: continue
|
|
seen.add(v)
|
|
src, n = PAK, 'stage\\' + v.split('+')[-1]
|
|
if '+' in v and 'DefTables' in v:
|
|
src, n = disc_root() + '/hidden/DefTables.pak', v.split('+')[-1]
|
|
print()
|
|
try: dump('%s (via %s)' % (n, fn), parse(read_entry(src, name_hash(n))), a.limit)
|
|
except Exception as e: print('=== %s -> unresolved: %s' % (n, e))
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|