chore: retire the last dead paths and names from the consolidation #47
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
21
tools/re-capture/disc.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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]")))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" "$@"; }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user