archive_naming.py already harvests 6027 candidate names under 16 prefixes, and testing its candidate set directly shows it names all 24 StageParameter_S<NN> objects, 24/24. The previous entry presented that naming as new -- it is not. What was new was the identification (which object is which stage, the shared _Tutorial table, IsBoss16Enable = S16), not the method. The real gap, now closed: the sweep reported only per-archive percentages and never emitted WHICH entry got which name, which is exactly why nobody could say the settings objects were StageParameter_*. It now prints the resolved name families per archive -- 6573 named entries, 1631 families disc-wide. Determinism caught again by the verify loop: the resolved map was built by iterating a set, so collided hashes picked a different winner each run. Now iterated sorted(). Second time in two iterations -- any map built from a set needs a sort. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
86 lines
4.0 KiB
Python
86 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""How much of each pak's TOC can be named from the asset strings on the disc.
|
|
|
|
Harvests every plausible asset-name string from every archive, hashes each under
|
|
the known path prefixes, and reports per archive how many of its entries are
|
|
explained. Regenerates docs/re/data/archive-naming.txt.
|
|
"""
|
|
import sys, os, glob, re, collections
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
import unitgroup as U
|
|
from unit_substructures import pak_entries
|
|
|
|
EXT = r'(prt|t32|rat|tbl|xpr|ttc|ttf|TTC|TTF|bin|col|rgn|prm)'
|
|
PRE = ['', '2d\\', 'ui\\', 'hud\\', 'Stage\\', 'stage\\', 'message\\', 'language\\',
|
|
'prt\\', 'view\\'] + [l + '\\' for l in ('eng', 'jpn', 'fra', 'deu', 'ita', 'esp')]
|
|
|
|
def main():
|
|
paks = sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True))
|
|
names = set()
|
|
toc = {}
|
|
for pk in paks:
|
|
ents = pak_entries(pk)
|
|
toc[os.path.basename(pk)] = [h for h, b in ents]
|
|
for h, b in ents:
|
|
for m in re.finditer((r'[A-Za-z0-9_\\.\-]{4,60}\.' + EXT).encode(), b):
|
|
names.add(m.group(0).decode('latin-1'))
|
|
|
|
H = set()
|
|
for n in names:
|
|
for p in PRE:
|
|
H.add(U.name_hash(p + n))
|
|
|
|
print("# How much of each pak's TOC can be named from the disc's own strings")
|
|
print("# Regenerate: python3 tools/re-capture/archive_naming.py")
|
|
print("# See docs/re/structures/archive-naming.md")
|
|
print("\n candidate asset-name strings harvested: %d" % len(names))
|
|
print(" path prefixes tried: %d" % len(PRE))
|
|
print("\n archive entries named pct")
|
|
rows = sorted(((k, v) for k, v in toc.items()), key=lambda r: -len(r[1]))
|
|
for nm, ent in rows:
|
|
nd = sum(1 for h in ent if h in H)
|
|
print(" %-30s %6d %6d %5.1f%%" % (nm, len(ent), nd, 100.0 * nd / len(ent) if ent else 0))
|
|
|
|
# WHICH entries got named — the per-archive content index. The percentage
|
|
# above is a statistic; this is the part that is actually usable.
|
|
NAMES = {}
|
|
for n in sorted(names): # sorted: a set's order varies per run
|
|
for p in PRE:
|
|
NAMES.setdefault(U.name_hash(p + n), p + n)
|
|
total = sum(1 for ent in toc.values() for h in ent if h in NAMES)
|
|
print("\n## What each archive is made of (resolved names, digits collapsed to #)")
|
|
print(" total named entries across the disc: %d" % total)
|
|
for nm, ent in rows:
|
|
res = [NAMES[h] for h in ent if h in NAMES]
|
|
if not res:
|
|
continue
|
|
fam = collections.Counter(
|
|
re.sub(r'\d+', '#', r.rsplit('\\', 1)[-1]) for r in res)
|
|
print("\n %s %d named, %d families" % (nm, len(res), len(fam)))
|
|
for k, c in sorted(fam.items(), key=lambda kv: (-kv[1], kv[0])):
|
|
print(" x%-4d %s" % (c, k))
|
|
|
|
def _ix(t): return U.ixud_hash([ord(c) for c in t])
|
|
FN = [('name_hash', U.name_hash), ('tag_hash', U.tag_hash), ('ixud_hash', _ix)]
|
|
SUB = ['', '2d\\', 'eng\\', 'Data\\', 'ArmsSt\\', 'Marker\\']
|
|
print("\n## CONTROL is the unnameable pair keyed by a DIFFERENT hash?")
|
|
print(" (6 prefixes x 3 hash families; the menu paks are the positive control)")
|
|
for nm in ['GP_MAIN_GAME_E2D.pak', 'GP_READY_ROOM.pak', 'GP_TITLE.pak', 'GP_PAUSE_MENU.pak']:
|
|
S = set(toc[nm])
|
|
for fname, f in FN:
|
|
best = (0, '')
|
|
for p in SUB:
|
|
n = sum(1 for v in names if f(p + v) in S)
|
|
if n > best[0]: best = (n, p or '<bare>')
|
|
print(" %-22s %-10s %4d / %-5d %s" % (nm, fname, best[0], len(S), best[1]))
|
|
print("\n## CONTROL the executable holds almost no asset names")
|
|
print(" sylpheed.db `strings`: 7140 rows, of which 2 look like asset paths")
|
|
print(" Data\\gmicon002_2.t32 and Data\\gmicon006_2.t32")
|
|
for n in ['Data\\gmicon002_2.t32', 'Data\\gmicon006_2.t32', 'gmicon002_2.t32']:
|
|
print(" %-28s resolves anywhere: %s" % (n, U.name_hash(n) in H))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|