chore: retire the last dead paths and names from the consolidation #47

Merged
fabi merged 3 commits from chore/retire-leftover-names into main 2026-09-17 05:11:37 +00:00
60 changed files with 199 additions and 113 deletions

View File

@@ -6,7 +6,7 @@
//! or a mis-decode in our tables.
//!
//! ```text
//! cargo run --release -p xenia-cpu --example decode_table_check -- <table.txt>
//! cargo run --release -p sylpheed-ppc --example decode_table_check -- <table.txt>
//! ```
//! where each line is `0xWORD name`.
use std::io::BufRead;

View File

@@ -5542,12 +5542,16 @@ mod font_sample_tests {
/// panicking (regression for the egui `set_fonts` crash).
#[test]
fn real_font_rasterizes() {
let pak = Path::new("/tmp/sylph_extract/dat/movie/eng.pak");
let Some(disc) = std::env::var_os("SYLPHEED_DISC") else {
eprintln!("SKIP: SYLPHEED_DISC not set");
return;
};
let pak = Path::new(&disc).join("dat/movie/eng.pak");
if !pak.exists() {
eprintln!("SKIP: extract not present");
eprintln!("SKIP: {} not present", pak.display());
return;
}
let arc = sylpheed_formats::PakArchive::open(pak).unwrap();
let arc = sylpheed_formats::PakArchive::open(&pak).unwrap();
let bytes = arc.read_by_hash(0x5cd0_fca6).unwrap().unwrap();
assert!(sylpheed_formats::font::is_font(&bytes));
let img = render_font_sample(&bytes, "The quick brown fox 0123", 34.0)

View File

@@ -32,7 +32,7 @@ pub struct Xex2SecurityInfo {
pub load_address: u32,
pub export_table_address: u32,
pub image_flags: u32,
/// Encrypted session key (decrypted with retail/devkit key to get actual session key).
/// Encrypted session key (decrypted with the retail key to get the actual session key).
pub aes_key: [u8; 16],
pub page_descriptors: Vec<Xex2PageDescriptor>,
}

View File

@@ -493,14 +493,13 @@ fn load_basic_compressed(source: &[u8], info: &FileFormatInfo) -> io::Result<Vec
}
/// Xbox 360 retail AES key for XEX2 session key decryption.
///
/// The only key this loader needs: Project Sylpheed ships as a retail XEX2. The
/// devkit (all-zero) and XEX1 keys that other loaders try are deliberately absent.
const XEX2_RETAIL_KEY: [u8; 16] = [
0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91,
];
/// Xbox 360 devkit AES key (all zeros).
#[allow(dead_code)]
const XEX2_DEVKIT_KEY: [u8; 16] = [0u8; 16];
/// AES-128-CBC decryption with zero IV (matching Xbox 360 XEX decryption).
#[tracing::instrument(skip_all, fields(bytes = input.len()))]
fn aes_decrypt_cbc(key: &[u8; 16], input: &[u8]) -> Vec<u8> {
@@ -528,7 +527,6 @@ fn aes_decrypt_cbc(key: &[u8; 16], input: &[u8]) -> Vec<u8> {
}
/// Derive the session key by decrypting the XEX's aes_key field with the retail key.
/// Falls back to devkit key if retail produces invalid results.
fn derive_session_key(header: &Xex2Header) -> [u8; 16] {
let sec = match &header.security_info {
Some(s) => s,

View File

@@ -1,10 +1,10 @@
//! Analysis-side goldens: every row in the xenia-cpu fixtures must
//! Analysis-side goldens: every row in the sylpheed-ppc fixtures must
//! round-trip cleanly through the [`sylpheed_xexdb::ppc`] shim. This
//! pins the shim's behaviour to the canonical `sylpheed_ppc::disasm::format`
//! output so that any future refactor of the shim layer surfaces here.
//!
//! Loads the same JSON fixtures committed under
//! `crates/xenia-cpu/tests/golden/`. No separate analysis-side fixture
//! `crates/sylpheed-ppc/tests/golden/`. No separate analysis-side fixture
//! files — the cpu canon is the source of truth.
use std::path::PathBuf;

View File

@@ -27,6 +27,30 @@ that is the correct reading: **each unresolved path here is work outstanding.**
---
## ✅ Closed 2026-09-16
**The end state is reached: two live repositories.** `Syplheed-Reborn`,
`Sylpheed-Godot`, `xenia-rs` and `xex2tractor` are archived read-only on Gitea
(by the human, 2026-09-16), not deleted. The harvests are on `main`: the DuckDB
tool (`crates/sylpheed-xexdb`, #32), the import-thunk naming (#35, re-landed as
#39), the xex2tractor reference files (#30) and the orphan assets (#34). The last
reference files from the project root follow in #46. `sylpheed.db` lives in this
repository's root and was rebuilt from scratch.
| phase | outcome |
|---|---|
| **5** archive | ✅ the four repos archived; `xenia-rs` and `sylpheed-reborn` clones deleted from this machine (the other two never had one here) |
| **7** drops | ✅ `Sylpheed/target/` deleted; `agent-backups/`, `stock-oracle/`, `texcompare/`, `ship_render/`, the `pi/*` branches and the root Canary logs are not on this machine |
| fork | ✅ Canary's default branch is `sylpheed-re`, no longer `xenia-rs` |
Still the human's, unchanged: the **retention** question (all repositories are
public; `docs/re/captures/` holds game screenshots) and the **history rewrite**,
recommended against in Phase 5.
The rest of this page is the working record as it stood during the move. Its
paths into the retired repositories no longer resolve because those clones are
gone, not because work is outstanding.
## Where things stand
| repo | server | local tree | tracked | disposition |
@@ -127,7 +151,7 @@ Four things exist only there.
| take | why |
|---|---|
| **XEX2 devkit + XEX1 retail master keys** | `xenia-xex` hardcodes retail only; devkit is present but `#[allow(dead_code)]` and XEX1 is absent. xex2tractor tries all three in a validating loop. |
| ~~**XEX2 devkit + XEX1 retail master keys**~~ | `xenia-xex` hardcodes retail only; devkit is present but `#[allow(dead_code)]` and XEX1 is absent. xex2tractor tries all three in a validating loop. ✅ **Not taken, by decision (human, 2026-09-16):** Project Sylpheed is a retail XEX2, so the retail key is the only one needed — the dead devkit key was removed from `sylpheed-xex` instead. |
| ~~**`extract -r`**~~ | 🔴 **RUN, AND THE INFERENCE WAS WRONG** — see the gate below. Optional, and dangerous if it is ever mistaken for the `.pe`. |
| **`doc/xex2_format.md`** (39 KB) and **`doc/xbox360_exports.json`** (938 KB, 2,913 exports across xboxkrnl / xam / xbdm) | byte-identical to untracked loose copies in the project root. Tracked by **no repo**. |
| **`LICENSE`** (MIT) | Sylpheed has none. |
@@ -413,7 +437,7 @@ citations" turn out to be one.)
is a deletion on the human's disk, and rebuilding it is hours, so it stays until
asked for.
## Phase 5 — Verify the invariant, then archive ⬜ **NOT STARTED**
## Phase 5 — Verify the invariant, then archive ✅ **DONE 2026-09-16**
For each of `Syplheed-Reborn`, `Sylpheed-Godot`, `xenia-rs`, `xex2tractor`:
re-run the containment check **at that moment** — not from this page's ledger,
@@ -472,7 +496,7 @@ Tracked by nobody today, and tools or references by the two-repo rule:
| `scratch/xbg7/extract.py` — clean-room XBG7 scanner | superseded by `sylpheed_formats::mesh`; keep as history or drop |
| `Sylpheed-try/…/examples/_voice_span.rs` — 46-line untracked probe | keep or drop |
## Phase 7 — Intended drops ⬜ **NOT STARTED**
## Phase 7 — Intended drops ✅ **DONE 2026-09-16**
Stated so that nothing is dropped by omission.
@@ -490,7 +514,7 @@ Stated so that nothing is dropped by omission.
---
# ▶️ STATUS 2026-09-13 — paused here, for the other machine to finish
# STATUS 2026-09-13 — paused here, for the other machine to finish (superseded: see ✅ Closed 2026-09-16 at the top)
**Six of eight phases are done and pushed.** Everything below is committed;
nothing is left in a working tree. Five pull requests are open and **none has
@@ -506,8 +530,8 @@ been merged** — merging is the human's, and #32 contains #30 and #31.
| **4** captures + the missing check | ✅ done | **PR #33** `fix/capture-citations` |
| **6** adopt the orphan assets | ✅ done | **PR #34** `chore/adopt-orphan-assets`, + Canary `99cc72662` |
| — containers and agents | ✅ done | **PR #31** `docs/containers-setup` |
| **5** archive the four repos | ⬜ **open — needs the human** | |
| **7** intended drops | ⬜ **open — partly needs the human** | |
| **5** archive the four repos | ✅ done 2026-09-16 | archived by the human |
| **7** intended drops | ✅ done 2026-09-16 | |
## 🔴 Five claims on this page were wrong. Read these before trusting the rest.

View File

@@ -60,7 +60,7 @@ dialogue folds into L/R, so this changes how speech sits against music — an
aesthetic judgement, not a container detail. Pin it explicitly and record it,
exactly as MISSION §6 requires of the transcode command itself.
[mac]: https://git.mc02.dev/fabi/Syplheed-Reborn/src/branch/main/docs/re/structures/movie-audio-channels.md
[mac]: ../re/structures/movie-audio-channels.md
## 2. Engine routing — Godot writes a WAV instead of a device

View File

@@ -3579,8 +3579,8 @@ matches build 4. The conclusions are unchanged, the indices are not.
**So the next step is the guest code**, not the file: the splash draw path from
the emulator-era work (`sub_821CC7A0`, item vtable `0x820b30b4`) submits with
exactly the PS hash `E59B2B3D` this capture sees, and `xenia-rs/sylpheed.db` is
available in the container.
exactly the PS hash `E59B2B3D` this capture sees, and `sylpheed.db` (repository
root, queried with `tools/zq.py`) is available.
**And a second screen is NO LONGER BLOCKED, but it is not routine either.** The
main menu has been reached (screenshot in

View File

@@ -101,13 +101,14 @@ XEX load, identical across our emulator and canary.
`--dump-addr`) went with that emulator when it was retired on 2026-09-16. For static
`.rdata` reads that `--dump-addr` served, read the `.pe` directly: it is a flat VA dump,
file offset = `VA − 0x82000000`.
- **Oracle (correctness ground truth):** canary — the **Wine cross-build**
`xenia-canary/build-cross/bin/Windows/Debug/xenia_canary.exe` (the native Linux ELF
**crashes / does not run** — do not use it). This is the only emulator that reaches the
in-game menu; our retired `xenia-rs` never got past the intro video. Use canary to *observe output*
(capture its framebuffer for texture colours), not usually to instrument code — though its
`build-cross` toolchain does compile, so small C++ probes + rebuild are possible when needed.
Run **muted, one emulator process at a time**, point it at the real ISO (not the symlink).
- **Oracle (correctness ground truth):** canary, in either of two builds — the **Wine
cross-build** in `xenia-canary/build-cross/bin/Windows/Debug/` (launched by
`run-canary-safe.sh`) or the **native Linux build** in `xenia-canary-native/` (launched by
`run-canary-native-safe.sh`). This is the only emulator that reaches the in-game menu; our
retired `xenia-rs` never got past the intro video. Use canary to *observe output*
(capture its framebuffer for texture colours), not usually to instrument code — though both
builds compile, so small C++ probes + rebuild are possible when needed.
Run **muted, one emulator process at a time**, point it at the real ISO.
> ⚠️ **VA-equality caveat:** join **code** by PC (fixed), but **never** assume a data VA
> holds the same bytes across emulators — allocators differ. Compare data by content/layout.

View File

@@ -115,7 +115,7 @@ extraction limit.
field registrations (`name → offset`) and default-init stores
(`offset → value`), join them — heavy, and offset↔name correlation is
error-prone. (B, runtime) dump a loaded craft object's registry
(`obj+64`) / struct from xenia-rs or Canary — a fully-loaded craft already
(`obj+64`) / struct from Canary — a fully-loaded craft already
holds every default; one dump yields `name → value`. **B is the efficient
finish** given the framework is generic; A's framework map above is the
prerequisite either way.

View File

@@ -172,7 +172,7 @@ in guest memory that never changes.
The question is now narrow: **which guest PC is the spinning thread executing?**
Canary knows every `XThread`'s PPC context, so a diagnostic that dumps each
thread's guest PC on demand (or after N seconds without a frame) would name the
loop, and `xenia-rs/sylpheed.db` can then say what function it is in. That is a
loop, and `sylpheed.db` (`tools/zq.py fn <pc>`) can then say what function it is in. That is a
`build-canary` run plus a reproduction — the cost is worth stating up front, and
it is the only avenue that does not involve guessing.

View File

@@ -1251,7 +1251,7 @@ and it is worth recording rather than re-attempting the same way.
(`%rsi` here, given `mov 0x110(%rsi),%rbx`), so the **guest PC is recoverable
from the context block** at the moment of the write. Reading the right offset out
of `$rsi` would name the guest instruction. That needs Xenia's context layout —
which is in the xenia-rs sources on this box — and is a separate, tractable
`PPCContext` in Canary's `src/xenia/cpu/ppc/ppc_context.h` — and is a separate, tractable
piece of work rather than another blind run.
## 🟡 `sub_8226E458` is a splice — but I have not shown it touches the trigger queue

View File

@@ -379,7 +379,7 @@ nothing rises above chance.
❔ **What would actually settle it** is the code — find what reads a `REGN`
object in the executable and watch which fields it dereferences. That is static
PE work (`/work/*.pe`, offset = VA − 0x82000000) of the same kind that cracked
PE work (the flat `.pe` in the project root, offset = VA − 0x82000000) of the same kind that cracked
the `.slb` packing phase, and it is the honest next step rather than a
twenty-first correlation.
---

View File

@@ -322,7 +322,7 @@ self-describing.
`game:\dat\sound.pak+` and `SETTINGS.PARAM` is `Pj_Silph.xgs`, so there is code
that opens a bank by name and seeks to its data; the constant, or the table it
indexes, should be visible there. That is static PE work
(`/work/*.pe`, offset = VA − 0x82000000), not another pass over the archive —
(the flat `.pe` in the project root, offset = VA − 0x82000000), not another pass over the archive —
this page has taken the byte-level evidence about as far as it goes.

View File

@@ -244,7 +244,7 @@ that gets adopted as a rule if only the agreeing five are counted.
## A foothold in the guest code, and what it is not
The remaining avenue is the code, and `xenia-rs/sylpheed.db` is in the container.
The remaining avenue is the code, and `sylpheed.db` is in the repository root (`tools/zq.py`).
What is established so far is small, and stated so it is not mistaken for more:
* **The item class from the splash-era work is real.** Vtable `0x820b30b4` is

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