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>
77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Every `opt ` name in every RATC bundle on the disc, by extension -- and the
|
|
`.tan` frame sequences among them.
|
|
|
|
Written to close the reach caveat in docs/re/structures/ratc-child-names.md:
|
|
60 of 18 002 RATC children carry no `opt ` block of their own. They are not
|
|
children. They are the ten frames of the disc's only `.tan` resource, and one
|
|
`opt ` block names the whole run.
|
|
|
|
python3 tools/re-capture/ratc_opt_name_census.py
|
|
|
|
Reads $SYLPHEED_DISC/dat/*.pak directly (IPFB TOC + Z1/zlib entries), so it does
|
|
not depend on the Rust parser it is checking. Takes a few minutes.
|
|
"""
|
|
|
|
import struct, zlib, glob, os, collections, bisect
|
|
from disc import disc_root
|
|
MAG = (b"T8aD", b"RATC", b"ttcf", b"\x89PNG")
|
|
ext = collections.Counter()
|
|
tan_sites = []
|
|
opt_total = 0
|
|
DISC = disc_root()
|
|
for pakpath in sorted(glob.glob(f"{DISC}/dat/*.pak")):
|
|
base = pakpath[:-4]
|
|
pak = open(pakpath, "rb").read()
|
|
if pak[:4] != b"IPFB": continue
|
|
n = struct.unpack_from(">I", pak, 4)[0]
|
|
toc = [struct.unpack_from(">III", pak, 0x10 + 12*i) for i in range(n)]
|
|
segs = sorted(glob.glob(base + ".p[0-9][0-9]"))
|
|
if not segs: continue
|
|
data = b"".join(open(s, "rb").read() for s in segs)
|
|
for ei, (h, off, cs) in enumerate(toc):
|
|
raw = data[off:off+cs]
|
|
try:
|
|
b = zlib.decompress(raw[10:]) if raw[:2] == b"Z1" else raw
|
|
except Exception:
|
|
continue
|
|
if b[:4] != b"RATC": continue
|
|
names = []
|
|
p = b.find(b"opt ")
|
|
while p >= 0:
|
|
ln = struct.unpack_from(">I", b, p+4)[0] if p+8 <= len(b) else 0
|
|
if 0 < ln <= 64 and p+8+ln <= len(b):
|
|
nm = b[p+8:p+8+ln].decode('latin1', 'replace')
|
|
if nm and all(32 < ord(c) < 127 for c in nm):
|
|
names.append((p, nm)); opt_total += 1
|
|
ext[os.path.splitext(nm)[1].lower()] += 1
|
|
p = b.find(b"opt ", p+4)
|
|
offs, i = [], 4
|
|
while i + 4 <= len(b):
|
|
if b[i:i+4] in MAG:
|
|
offs.append(i); i += 4
|
|
else: i += 1
|
|
if not names or not offs: continue
|
|
npos = [p for p, _ in names]
|
|
# each child -> index of the nearest preceding opt
|
|
owner = collections.defaultdict(list)
|
|
for k, o in enumerate(offs):
|
|
j = bisect.bisect_left(npos, o) - 1
|
|
if j >= 0: owner[j].append(k)
|
|
for j, (p, nm) in enumerate(names):
|
|
if not nm.lower().endswith(".tan"): continue
|
|
ks = owner.get(j, [])
|
|
if not ks: continue
|
|
sizes = sorted({(offs[k+1] if k+1 < len(offs) else len(b)) - offs[k] for k in ks})
|
|
tan_sites.append((os.path.basename(pakpath), ei, nm, len(ks), sizes))
|
|
print(f"`opt ` blocks disc-wide: {opt_total}")
|
|
print("\nby extension:")
|
|
for e, c in ext.most_common(25):
|
|
print(f" {e or '(none)':10} x{c}")
|
|
print(f"\n.tan resources with >=1 child: {len(tan_sites)}")
|
|
seen = collections.Counter()
|
|
for t in tan_sites:
|
|
seen[(t[0], t[2], t[3], tuple(t[4]))] += 1
|
|
for (pk, nm, fr, sz), c in sorted(seen.items()):
|
|
print(f" {pk:24} {nm:30} frames={fr:3} sizes={list(sz)} x{c} bundles")
|