Files
Sylpheed/tools/re-capture/stagetbl.py
sim ac0ad579fd chore: retire the last dead paths and names from the consolidation
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>
2026-09-16 22:30:28 +02:00

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())