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>
This commit is contained in:
sim
2026-09-16 22:30:28 +02:00
parent 591527439d
commit e909c7c133
59 changed files with 169 additions and 107 deletions

View File

@@ -16,7 +16,7 @@ def nh(name):
a=(a-(q*MOD))&0xFFFFFFFF
return (((bb<<24)&0xFF000000)|(a&0xFFFFFF))&0xFFFFFFFF
EX="/home/fabi/RE Project Sylpheed/sylph_extract"
EX = os.environ.get("SYLPHEED_DISC") or exit("set SYLPHEED_DISC to the extracted disc (the directory holding dat/)")
def load_pak(base):
"""base like dat/movie/eng ; returns list of (hash,off,size), data bytes"""
pak=open(f"{EX}/{base}.pak","rb").read()

View File

@@ -7,6 +7,7 @@ whether the 34-name roster is shared, and whether `Type` really predicts the
field count. Regenerates docs/re/data/aiparams-census.txt.
"""
import sys, os, glob, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -32,7 +33,7 @@ def objects(pak):
return out
def main():
paks = sorted(p for p in glob.glob('/work/sylph_extract/**/*.pak', recursive=True)
paks = sorted(p for p in glob.glob(disc_root() + '/**/*.pak', recursive=True)
if os.path.basename(p).startswith('GP_MAIN_GAME_')
and '2D' not in os.path.basename(p))
print("# AIParams across the whole disc")

View File

@@ -6,6 +6,7 @@ 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
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -17,7 +18,7 @@ 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))
paks = sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True))
names = set()
decl = set()
nenum = collections.Counter()

View File

@@ -7,6 +7,7 @@ what points into the 131-record `Weapon` datasheet. Regenerates
docs/re/data/arsenal-chain.txt.
"""
import sys, os, glob, re, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -17,8 +18,8 @@ ITEMF = {'Dependency', 'MissionObjective', 'Model', 'Package',
'PlayerWeapon', 'Points', 'Power', 'Range'}
def main():
ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
ars = glob.glob(disc_root() + '/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
mg = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
slots = collections.defaultdict(dict); wids = set()
for h, b in pak_entries(mg):

View File

@@ -2,10 +2,12 @@
"""Every BGM bank's sub-wave structure: count, bytes, PsuedoBytesPerSec, seconds.
Static, disc-only. See docs/re/structures/bgm-two-stems.md."""
import sys, struct
sys.path.insert(0,"/work/Syplheed-Reborn/tools/re-capture")
exec(open("/work/Syplheed-Reborn/tools/re-capture/slb_segment_phase.py").read().split("def cmd_phases")[0])
pak=Pak("/work/sylph_extract/dat/sound.pak")
import os, sys, struct
from disc import disc_root
_SD = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SD)
exec(open(os.path.join(_SD, "slb_segment_phase.py")).read().split("def cmd_phases")[0])
pak=Pak(disc_root() + "/dat/sound.pak")
def bps(b, riff_at):
# PsuedoBytesPerSec at RIFF+0x20 (u32 LE), SampleRate at +0x24
return struct.unpack_from("<I", b, riff_at+0x20)[0], struct.unpack_from("<I", b, riff_at+0x24)[0]

View File

@@ -12,6 +12,7 @@ instead of a type name ('Yes' for a boolean, 'Vessel' for Generic.Type).
Regenerates docs/re/data/datasheet-schema.txt.
"""
import glob, os, sys
from disc import disc_root
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unit_substructures import pak_entries
import unitgroup as U
@@ -27,7 +28,7 @@ TYPES = {
}
def main():
pak = sorted(glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_D.pak', recursive=True))
pak = sorted(glob.glob(disc_root() + '/**/GP_MAIN_GAME_D.pak', recursive=True))
if not pak:
print('disc not mounted; nothing to do'); return
out = {}

21
tools/re-capture/disc.py Normal file
View File

@@ -0,0 +1,21 @@
"""Where the extracted disc is: `$SYLPHEED_DISC`, or a loud error.
The censuses in this directory used to glob `/work/sylph_extract`, a path that
stopped existing when `/work` became a clone of this repository. A glob over a
missing directory matches nothing, so those scripts printed empty results instead
of failing (see `name_block_bases.py`). Resolve the disc here so that cannot
happen quietly again.
"""
import os
import sys
def disc_root():
"""The extracted disc's root — the directory holding `dat/` and `hidden/`."""
root = os.environ.get("SYLPHEED_DISC")
if not root or not os.path.isdir(os.path.join(root, "dat")):
sys.exit(
f"SYLPHEED_DISC={root!r} is not an extracted disc (no dat/ inside). "
"Set it to the directory that holds dat/ and hidden/."
)
return root

View File

@@ -2,13 +2,16 @@
"""Dump the guest's DECOMPRESSED executable image out of live Xenia memory.
Why this exists: the static PPC route the corpus is built on ran against a
disassembly database at `/work/xenia-rs/sylpheed.db`, and that file is **not in
this container** — the same migration that took the Xenia storage root. Without
it, every finding that cites a `sub_82xxxxxx` is unre-checkable.
disassembly database at `/work/xenia-rs/sylpheed.db`, and that file was **not in
the container** — the same migration that took the Xenia storage root. Without
it, every finding that cites a `sub_82xxxxxx` was unre-checkable. (The database
now lives in this repository's root as `sylpheed.db`, rebuilt from the ISO by
`sylph-xexdb`, which decrypts and decompresses the XEX itself; query it with
`tools/zq.py`. This dump remains the way to get the image a *running* guest holds.)
`/disc/default.xex` cannot substitute: it is encrypted and LZX-compressed. Its
header is intact (`XEX2`, original PE name `default.pe`) and everything after is
noise — `strings` finds **zero** occurrences of `GamePart` in it.
`/disc/default.xex` cannot substitute on its own: it is encrypted and
LZX-compressed. Its header is intact (`XEX2`, original PE name `default.pe`) and
everything after is noise — `strings` finds **zero** occurrences of `GamePart` in it.
Xenia decompresses, decrypts and relocates the image at load, so a running guest
holds exactly the flat VA image the corpus calls the `.pe`. Dump it once and the

View File

@@ -8,10 +8,11 @@ Bit comes from the T8aD header; the name from the string immediately preceding
it (validated 17/18 on build 4 against the RATC child order).
"""
import struct, zlib, glob, re, os
from disc import disc_root
import numpy as np
from PIL import Image
NAME = re.compile(rb'[A-Za-z0-9_.]{2,31}\x00')
base = "/work/sylph_extract/dat/GP_TITLE"
base = disc_root() + "/dat/GP_TITLE"
stub = open(base + ".pak", "rb").read()
n = struct.unpack_from(">I", stub, 4)[0]
blob = b"".join(open(s, "rb").read() for s in sorted(glob.glob(base + ".p[0-9][0-9]")))

View File

@@ -7,6 +7,7 @@ preceding name is the ELEMENT's (opt) name and the child list is the SPRITE's.
This test uses the element name and says so.
"""
import struct, zlib, glob, os, re, collections
from disc import disc_root
NAME = re.compile(rb'[A-Za-z0-9_.]{2,31}\x00')
def entries(base):
@@ -25,7 +26,7 @@ def entries(base):
set_eff = set_noneff = clear_eff = clear_noneff = 0
examples = []
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for d in entries(pak[:-4]):
for m in re.finditer(b"T8aD", d):
o = m.start()

View File

@@ -12,11 +12,12 @@ prefix.
Regenerates docs/re/data/effect-homes.txt.
"""
import glob, os, re, subprocess, sys, collections
from disc import disc_root
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unit_substructures import pak_entries
import unitgroup as U
XPR = '/work/sylph_extract/hidden/resource3d'
XPR = disc_root() + '/hidden/resource3d'
FX = re.compile(r'(FxModel|EffectName|Effect_|ShellModel|CoverModel)')
def width(n):
@@ -34,7 +35,7 @@ def main():
home[name].add(os.path.basename(path))
bound = set()
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
for _h, b in pak_entries(pk):
if b[:4] != b'IDXD':
continue

View File

@@ -35,7 +35,7 @@ import sys
import numpy as np
from PIL import Image
CAP = "/work/docs/re/captures/title-builds"
CAP = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "docs", "re", "captures", "title-builds"))
REF_A = f"{CAP}/live-main-menu.png" # NEW GAME focused
REF_B = f"{CAP}/live-main-menu-options-focused.png" # OPTIONS focused
BUTTONS = ["NEW GAME", "LOAD GAME", "TUTORIAL", "OPTIONS", "EXTRAS"]

View File

@@ -1,13 +1,14 @@
import sys, glob, collections
S="/tmp/claude-1000/-home-fabi-RE-Project-Sylpheed/b113cc12-4769-4ed8-ad66-c2b48f800773/scratchpad"
sys.path.insert(0,S+"/wt-root/Syplheed-Reborn/tools/re-capture")
exec(open(S+"/regn_decode.py").read().split("# ── the object")[0])
import os, sys, glob, collections
from disc import disc_root
_SD = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SD)
exec(open(os.path.join(_SD, "regn_decode.py")).read().split("# ── the object")[0])
import unitgroup as U
print("# `Generic` is the per-FILE header record, not a table")
print("#")
print("# One `Generic` per IDXD pak entry; its schema is set by the file's TYPE.")
print("# Regenerate: see docs/re/structures/unit-datasheet-static.md")
for pk in sorted(glob.glob("/work/sylph_extract/**/GP_MAIN_GAME_*.pak",recursive=True)):
for pk in sorted(glob.glob(disc_root() + "/**/GP_MAIN_GAME_*.pak",recursive=True)):
E=pak_entries(pk); items=E.items() if isinstance(E,dict) else E
kinds=collections.Counter(); ids=set(); types=collections.Counter()
nidxd=0; man=eff=0; ctl_ok=ctl_bad=0; one_per=True

View File

@@ -6,6 +6,7 @@ per-slot ALLOW-LIST record whose positional (unnamed) fields are the ordered
candidate item names. Regenerates docs/re/data/hangar-loadouts.txt.
"""
import sys, os, glob, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -17,10 +18,10 @@ ITEMF = {'Dependency', 'MissionObjective', 'Model', 'Package',
SLOTS = ('Arm1', 'Arm2', 'Arm3', 'Nose')
def main():
ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
ars = glob.glob(disc_root() + '/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
unit_ids, char_ids = set(), set()
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
for h, b in pak_entries(pk):
if b[:4] != b'IDXD': continue
try: recs = U.parse(b)

View File

@@ -4,6 +4,7 @@
Regenerates docs/re/data/hud-config.txt.
"""
import sys, os, glob, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -14,10 +15,10 @@ PRE = ['', '2d\\', 'ui\\', 'hud\\'] + [l + '\\' for l in ('eng', 'jpn', 'fra', '
DEFAULT = ('HudResource.tbl', 'HudMarkerResource.tbl')
def main():
p2 = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E2D.pak', recursive=True)[0]
p2 = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E2D.pak', recursive=True)[0]
E = dict(pak_entries(p2))
H = set()
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
for h, b in pak_entries(pk):
H.add(h)
@@ -75,7 +76,7 @@ def main():
print(" %-30s %s" % (sq, " ".join("%s=%s" % (k, v) for k, v in sorted(n.items()))))
print("\n## ResourceTable -- 29 pairs, one per stage, with a single override")
for pk in sorted(glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_*2D.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/GP_MAIN_GAME_*2D.pak', recursive=True)):
for h, b in pak_entries(pk):
if b[:4] != b'IDXD': continue
for r in U.parse(b):

View File

@@ -20,11 +20,12 @@ The headline measurement (see docs/re/structures/idxd-unnamed-keys.md):
7 052 of them are < 0x10000 -- author-assigned ordinals / element ids
42 of them are >= 0x01000000 -- genuine tag_hash values
`SYLPHEED_DISC` (default /work/sylph_extract) points at the extracted disc.
`SYLPHEED_DISC` (required) points at the extracted disc.
"""
import argparse, collections, glob, os, re, struct, sys, zlib
from disc import disc_root
DISC = os.environ.get('SYLPHEED_DISC', '/work/sylph_extract')
DISC = disc_root()
# --------------------------------------------------------------------------- hashes

View File

@@ -10,6 +10,7 @@ across the 28 stages resolve, so this is a total mapping rather than a sample. 3
message names span more than one page; every page is printed.
"""
import collections
from disc import disc_root
import struct
import sys
@@ -86,7 +87,7 @@ def walk(b, base, nxt, sink):
def main():
ssb = sys.argv[1]
pak = sys.argv[2] if len(sys.argv) > 2 else \
'/work/sylph_extract/dat/GP_MAIN_GAME_E.pak'
disc_root() + '/dat/GP_MAIN_GAME_E.pak'
sys.path.insert(0, sys.path[0])
from unitgroup import read_entry # noqa: F401 (kept for parity)
import zlib

View File

@@ -17,6 +17,7 @@ The filter keys on SHAPE, not on a count: >=2 consecutive blocks whose fade is
0x??ffffff, whose tint is 0xffffffff, and whose scale words are 1..4000.
"""
import struct, zlib, glob, os, sys, collections, re
from disc import disc_root
def entries(base):
stub=open(base+'.pak','rb').read()
@@ -65,7 +66,7 @@ if not all(True for _ in [0]): sys.exit(1)
hist=collections.Counter(); nz=collections.Counter(); total=0
examples=collections.defaultdict(list)
for pak in sorted(glob.glob('/work/sylph_extract/dat/GP_*.pak')):
for pak in sorted(glob.glob(disc_root() + '/dat/GP_*.pak')):
base=pak[:-4]
for h,d in entries(base):
if b'RATC' not in d[:4] and d[:4]!=b'RATC': pass

View File

@@ -8,6 +8,7 @@ the two objects that belong to no documented family.
Regenerates docs/re/data/main-game-unnamed.txt.
"""
import sys, os, glob, re, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -64,7 +65,7 @@ def classify(shape):
return None, None
def main():
paks = sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True))
paks = sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True))
NAMES = name_map(paks)
mains = sorted(p for p in paks
if re.match(r'GP_MAIN_GAME_[A-Z]\.pak$', os.path.basename(p)))

View File

@@ -8,11 +8,12 @@ language pack, each holding Score_Easy / Score_Normal / Score_Hard with one
Run: python3 mission_scoring.py > ../../docs/re/data/mission-scoring.txt
"""
import sys, os, re, glob, collections
from disc import disc_root
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unit_substructures import pak_entries
import unitgroup as U
DAT = '/work/sylph_extract/dat'
DAT = disc_root() + '/dat'
KEY = 'RankScore_S'
DIFFS = ('Score_Easy', 'Score_Normal', 'Score_Hard')

View File

@@ -24,7 +24,7 @@ Two traps this encodes, both paid for:
once it tracks a transition it was not selected by.
"""
import os, struct, sys
sys.path.insert(0, "/work/Syplheed-Reborn/tools/re-capture")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gmem
def scan(path, val):

View File

@@ -22,11 +22,12 @@ number before it did:
across six paks. They are collapsed into one family here.
"""
import sys, glob, struct, re, collections
import os, sys, glob, struct, re, collections
from disc import disc_root
S="/tmp/claude-1000/-home-fabi-RE-Project-Sylpheed/b113cc12-4769-4ed8-ad66-c2b48f800773/scratchpad"
sys.path.insert(0,S+"/wt-root/Syplheed-Reborn/tools/re-capture")
exec(open(S+"/regn_decode.py").read().split("# ── the object")[0])
_SD = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SD)
exec(open(os.path.join(_SD, "regn_decode.py")).read().split("# ── the object")[0])
Hh=lambda b,o: struct.unpack_from(">H",b,o)[0]
def fam(n): return re.sub(r'_[DEFIJS]2D\.pak$','_2D.pak',n)
keys=collections.Counter(); kfam=collections.defaultdict(set)
@@ -37,7 +38,7 @@ def take(b,o,base,lang):
if Hh(b,o+8)==0: hi_zero+=1
k=Hh(b,o+10); keys[k]+=1; kfam[k].add(base)
if lang: langsets[lang].add(k)
ROOT = sys.argv[1] if len(sys.argv) > 1 else "/work/sylph_extract"
ROOT = sys.argv[1] if len(sys.argv) > 1 else disc_root()
for f in sorted(glob.glob(ROOT + "/**/*.pak", recursive=True)):
try: E=pak_entries(f)
except Exception: continue

View File

@@ -6,6 +6,7 @@ every texture). CONTROL: it must reproduce the three known fallback elements of
GP_TITLE build 7 (indices 8, 13, 14) and find ptlogo_eff3 among them.
"""
import struct, zlib, glob, os, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
@@ -56,7 +57,7 @@ def has_plateau(poses):
# ---- CONTROL -------------------------------------------------------------
ctl = {i: (n, p) for i, n, p in elements(
dict((i, d) for i, h, d in entries("/work/sylph_extract/dat/GP_TITLE"))[7])}
dict((i, d) for i, h, d in entries(disc_root() + "/dat/GP_TITLE"))[7])}
fb = sorted(i for i, (n, p) in ctl.items() if not has_plateau(p))
print(f"CONTROL GP_TITLE build 7: fallback elements {fb} (want [8, 13, 14])")
print(f" index 8 is {ctl[8][0]!r} (want ptlogo_eff3.t32)")
@@ -66,7 +67,7 @@ assert fb == [8, 13, 14] and ctl[8][0].startswith("ptlogo_eff3"), "control faile
tot = fb_n = 0
by_name = collections.Counter()
worst = []
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
base = pak[:-4]
for i, h, d in entries(base):
for idx, name, poses in elements(d):
@@ -119,7 +120,7 @@ def elements_t(d):
alpha = lambda p: (p[0] >> 24) & 0xff
stats = collections.Counter()
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for i, h, d in entries(pak[:-4]):
for idx, name, times, poses in elements_t(d):
if has_plateau(poses): continue

View File

@@ -1,6 +1,7 @@
"""Plateau-less elements whose LAST keyframe is visible -- do they contradict
the entry->hold->exit model, or are they 'slides in and stops'?"""
import struct, zlib, glob, os, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
def entries(base):
stub=open(base+".pak","rb").read()
@@ -41,7 +42,7 @@ def elements(d):
alpha=lambda q:(q[0]>>24)&0xff
mono=stuck=other=0
ex=[]
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for d in entries(pak[:-4]):
for nm,p in elements(d):
if len(p)==1: continue

View File

@@ -9,11 +9,12 @@ in the layout cutscene-message-table.md already documents.
Run: python3 preset_messages.py > ../../docs/re/data/preset-messages.txt
"""
import sys, os, re, glob, collections
from disc import disc_root
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unit_substructures import pak_entries
import unitgroup as U
DAT = '/work/sylph_extract/dat'
DAT = disc_root() + '/dat'
PAK = os.path.join(DAT, 'GP_MAIN_GAME_E.pak')
MARKER = 'ORDOR_SQUADRON_EXTENDED'
STAGES = ['S%02d' % i for i in list(range(1, 17)) + list(range(18, 30))]

View File

@@ -4,6 +4,7 @@
Regenerates docs/re/data/prt-parts.txt.
"""
import sys, os, glob, re, struct, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -33,7 +34,7 @@ def main():
elem2d = collections.defaultdict(set)
dirs = collections.Counter()
rat = collections.defaultdict(set)
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
base = os.path.basename(pk)
for h, b in pak_entries(pk):
H[h].add(base)

View File

@@ -14,11 +14,12 @@ 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 = os.environ.get("SYLPHEED_DISC", "/work/sylph_extract")
DISC = disc_root()
for pakpath in sorted(glob.glob(f"{DISC}/dat/*.pak")):
base = pakpath[:-4]
pak = open(pakpath, "rb").read()

View File

@@ -5,6 +5,7 @@ scored disc-wide. What CAN be quantified is the blast radius: how often the
rules disagree, and how often each returns a pose that draws nothing.
"""
import struct, zlib, glob, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
def entries(base):
stub=open(base+".pak","rb").read()
@@ -48,7 +49,7 @@ def dwell(t,p):
alpha=lambda q:(q[0]>>24)&0xff
n=agree=0
inv=collections.Counter(); deg=collections.Counter()
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for d in entries(pak[:-4]):
for t,p in elements(d):
if len(p)==1: continue

View File

@@ -21,6 +21,7 @@ the first version of this control did exactly that and reported 0 rotations for
screen that plainly has two. `screen list` prints the mapping.
"""
import struct, zlib, glob, os, sys, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
def entries(base):
@@ -64,8 +65,8 @@ def rotated(d):
return out, total
# ---- CONTROL -------------------------------------------------------------
t = dict(entries("/work/sylph_extract/dat/GP_TITLE"))
g = dict(entries("/work/sylph_extract/dat/GP_DIALOG"))
t = dict(entries(disc_root() + "/dat/GP_TITLE"))
g = dict(entries(disc_root() + "/dat/GP_DIALOG"))
ct, _ = rotated(t[4]); cg, _ = rotated(g[2]) # build 0 == entry 2, see the note above
print(f"CONTROL GP_TITLE build 4 (nested rotations only): {len(ct)} top-level rotated (want 0)")
print(f"CONTROL GP_DIALOG build 0: {len(cg)} top-level rotated (want 2)")
@@ -75,7 +76,7 @@ assert len(ct) == 0 and len(cg) == 2, "control failed"
# ---- census --------------------------------------------------------------
tot = rot = 0
per_pak = collections.Counter(); names = collections.Counter()
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for i, d in entries(pak[:-4]):
r = rotated(d)
if not r: continue

View File

@@ -4,7 +4,7 @@
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 XENIA_PAD_FILE=/tmp/xenia_pad.txt
export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages
SD=/tmp/claude-1000/-home-fabi-RE-Project-Sylpheed/b113cc12-4769-4ed8-ad66-c2b48f800773/scratchpad/wt-root/Syplheed-Reborn/tools/re-capture
SD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PATH="$SD/bin:$PATH"
OUT=/tmp/route; rm -rf $OUT; mkdir -p $OUT
pad(){ python3 "$SD/pad.py" "$@"; }

View File

@@ -4,6 +4,7 @@
Regenerates docs/re/data/script-manifest.txt.
"""
import sys, os, glob, re, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -11,7 +12,7 @@ import unitgroup as U
from unit_substructures import pak_entries
def main():
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
mg = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
E = dict(pak_entries(mg))
recs = U.parse(E[U.name_hash('Stage\\script.tbl')])
@@ -34,7 +35,7 @@ def main():
print(" %-22s %r" % (n, v))
idx = {}
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
idx[os.path.basename(pk)] = {h for h, b in pak_entries(pk)}
targets = ['MissionDialogMessage.tbl', 'MissionDialog_local_string.tbl',
'pgmsg_start.prt', 'pgmsg_end.prt', 'pgmsg_update.prt',

View File

@@ -14,6 +14,7 @@ See docs/re/menu-audio-cues.md.
Decode with: ffmpeg -i out.riff out.wav
"""
import os, struct, sys
from disc import disc_root
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
exec(open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"slb_segment_phase.py")).read().split("def cmd_phases")[0])
@@ -41,7 +42,7 @@ def main():
ch = int(a[3]) if len(a) > 3 else 1
rate = int(a[4]) if len(a) > 4 else 48000
out = a[5] if len(a) > 5 else f"{name.split('.')[0]}_{off:#x}.riff"
disc = os.environ.get("SYLPHEED_DISC", "/work/sylph_extract")
disc = disc_root()
pak = Pak(os.path.join(disc, "dat", "sound.pak"))
i = pak.find(name)
if i is None:

View File

@@ -21,7 +21,7 @@ Subcommands
chain <name> [n] the tiling arithmetic across n consecutive entries
Usage
SYLPHEED_DISC=/work/sylph_extract python3 slb_segment_phase.py verify
SYLPHEED_DISC=/path/to/extracted/disc python3 slb_segment_phase.py verify
"""
import bisect

View File

@@ -13,13 +13,14 @@ a script message `MSG_<X>` plays the cue `VOICE_<X>`, and the bank file is
`jpn\\Voice\\VOICE_<X>.slb`. See docs/re/structures/sound-cue-table.md.
"""
import collections
from disc import disc_root
import os
import re
import struct
import sys
import zlib
TABLES = '/work/sylph_extract/dat/tables.pak'
TABLES = disc_root() + '/dat/tables.pak'
def be32(b, o):
@@ -98,7 +99,7 @@ def wstr(b, pool, off):
def captions(lang='E'):
"""`MSG_*_<page>_<line>` -> text, from the pack's IXUD field tables."""
pak = '/work/sylph_extract/dat/GP_MAIN_GAME_%s.pak' % lang
pak = disc_root() + '/dat/GP_MAIN_GAME_%s.pak' % lang
out = {}
for b in pak_entries(pak).values():
if b[:4] != b'IXUD':
@@ -124,7 +125,7 @@ def captions(lang='E'):
def demo_messages(lang='E'):
"""The cutscene message table: id -> per-page (speaker, face, dur, cue)."""
pak = '/work/sylph_extract/dat/GP_MAIN_GAME_%s.pak' % lang
pak = disc_root() + '/dat/GP_MAIN_GAME_%s.pak' % lang
out = {}
for b in pak_entries(pak).values():
if b[:4] != b'IDXD' or b'MSG_DEMO' not in b:

View File

@@ -16,8 +16,8 @@ alpha > 0 changes:
so each run yields calibration points at t = 15, 45 and the splash's end.
"""
import sys, collections
sys.path.insert(0, '/work/tools/re-capture')
import os, sys, collections
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from quads_per_frame import quads
def batch_runs(path, lo=0, hi=400):

View File

@@ -6,6 +6,7 @@ player-craft mesh; the Arsenal pak's 168 stage-scoped entries say the same thing
from the other side. Regenerates docs/re/data/stage-numbering.txt.
"""
import sys, os, glob, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -13,8 +14,8 @@ import unitgroup as U
from unit_substructures import pak_entries
def main():
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
mg = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
ars = glob.glob(disc_root() + '/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
H = {h for h, b in pak_entries(mg)}
E = {h: b for h, b in pak_entries(ars)}

View File

@@ -11,10 +11,11 @@ docs/re/structures/unit-group-table.md and tools/re-capture/unitgroup.py.
# 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 = '/work/sylph_extract/dat/GP_MAIN_GAME_E.pak'
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."""
@@ -63,7 +64,7 @@ def main():
seen.add(v)
src, n = PAK, 'stage\\' + v.split('+')[-1]
if '+' in v and 'DefTables' in v:
src, n = '/work/sylph_extract/hidden/DefTables.pak', v.split('+')[-1]
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))

View File

@@ -4,6 +4,7 @@
Regenerates docs/re/data/turret-coverarea.txt.
"""
import sys, os, glob, re, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -30,7 +31,7 @@ def wclass(w):
return w or '<empty>'
def main():
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
mg = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
units = units_of(mg)
rows = [(gn.get('ID'), gn.get('Type'), U.named(r))
for recs, gn in units for r in recs if TUR.fullmatch(r['squadron'])]
@@ -71,7 +72,7 @@ def main():
% (m(n), n.get('Frame'), n.get('YawLimit'), n.get('WeaponID', '')))
wid, mounted, vers = set(), set(), collections.Counter()
for pk in sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)):
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
for h, b in pak_entries(pk):
if b[:4] != b'IDXD': continue
try: recs = U.parse(b)

View File

@@ -16,7 +16,7 @@ import struct
import sys
from collections import defaultdict
sys.path.insert(0, "/home/fabi/RE - Project Sylpheed/sylpheed-reborn/tools/re-capture")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gmem # noqa: E402
DELTAS = [0x00, 0x10, 0x08, 0x04, 0x0C, 0x14, 0x18, 0x20]

View File

@@ -23,7 +23,7 @@ import struct
import sys
from collections import defaultdict
sys.path.insert(0, "/home/fabi/RE - Project Sylpheed/sylpheed-reborn/tools/re-capture")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gmem # noqa: E402
DEF_VTABLE = 0x820AF844

View File

@@ -7,6 +7,7 @@ records: `Turret_NNN`, `Bridge_NNN`, `Hatch_NNN`, `ShieldGenerator_NNN`,
`Thruster_NNN`, `Versatile_NNN`. Regenerates docs/re/data/unit-substructures.txt.
"""
import sys, os, glob, re, collections
from disc import disc_root
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
@@ -48,7 +49,7 @@ def units_of(pak):
return out
def main():
pak = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
pak = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
units = units_of(pak)
print("# Sub-records inside the %d unit tables of %s" % (len(units), os.path.basename(pak)))
print("# Regenerate: python3 tools/re-capture/unit_substructures.py")

View File

@@ -30,6 +30,7 @@ which holds for 1160 of 1160 squadrons across the 28 stage tables present in
GP_MAIN_GAME_E.pak.
"""
import argparse, glob, os, struct, sys, zlib
from disc import disc_root
MODULUS, RECIP = 0x00FFF9D7, 0x80031493
@@ -155,10 +156,11 @@ def load_stage(pak, stage):
def main():
ap = argparse.ArgumentParser()
ap.add_argument('stage', nargs='?', default='S02')
ap.add_argument('--pak', default='/work/sylph_extract/dat/GP_MAIN_GAME_E.pak')
ap.add_argument('--pak', help='default: $SYLPHEED_DISC/dat/GP_MAIN_GAME_E.pak')
ap.add_argument('--all', action='store_true', help='every stage S01..S29')
ap.add_argument('--check', action='store_true', help='only report the Count*4+5 identity')
a = ap.parse_args()
a.pak = a.pak or disc_root() + '/dat/GP_MAIN_GAME_E.pak'
stages = ['S%02d' % n for n in range(1, 30)] if a.all else [a.stage]
held = total = 0
for st in stages:

View File

@@ -8,10 +8,11 @@ capture: constant, smoothly varying (a continuous quantity), or jumpy.
Usage: whatchanges.py <probe.bin> [name-substring] [max-report]
"""
import math
import os
import struct
import sys
sys.path.insert(0, "/home/fabi/RE - Project Sylpheed/sylpheed-reborn/tools/re-capture")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from flight_analyze import load # noqa: E402

View File

@@ -11,6 +11,7 @@ the change. It counts elements whose guessed rest pose is zero-scale and whose
alpha is non-zero, i.e. the ones the old code actually painted.
"""
import struct, zlib, glob, os, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
def entries(base):
stub=open(base+".pak","rb").read()
@@ -59,7 +60,7 @@ def dwell_pick(t,p):
return best[0]
alpha=lambda p:(p[0]>>24)&0xff
n=paint=0; where=collections.Counter(); ex=[]
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for i,h,d in entries(pak[:-4]):
for idx,name,t,p in elements_t(d):
if has_plateau(p): continue

View File

@@ -56,9 +56,8 @@ def _resolve_db():
"""Locate the database: `$SYLPHEED_DB` / `$SYLPH_XEXDB`, else `<repo root>/sylpheed.db`.
🔴 This used to be one hardcoded absolute path, and it pointed *inside*
`xenia-rs` -- a repository `docs/agents/CONSOLIDATION.md` archives in Phase 5
and drops in Phase 7. It resolved on exactly one machine and would have
started failing there too, with `duckdb` raising about a missing file rather
`xenia-rs` -- a repository that has since been archived. It resolved on
exactly one machine and would have started failing there too, with `duckdb` raising about a missing file rather
than anything saying why.
The database is a build artefact of several hundred MB and is not in the