Files
Sylpheed/crates/sylpheed-xexdb
Fabian Hamm 13c895abff feat(xexdb): port import-thunk naming + schema-wide foreign keys
Carries `xenia-rs` `harvest/import-thunk-naming` (b4f19f1) across into the
lifted crate. That work was found UNCOMMITTED in the retired repo's working
tree on 2026-09-14 and exists nowhere else: absent from `iterate-4A`, absent
from this crate as lifted. CONSOLIDATION.md Phase 5 ends with "delete the local
clone" and Phase 7 drops the emulator, so it had a deletion scheduled against
it.

Not a cherry-pick. The lift's base is exactly the harvest's parent (8401d4d),
so each file was merged three-way -- lifted vs base vs harvest -- which is what
made the port reviewable: 10 conflicts, 9 of them pure rustfmt reflow from the
lift's formatting pass, and 1 semantic.

The semantic one is insertion ORDER. `xdbf_achievements.image_id` is now a
foreign key onto `xdbf_images(id)`, so the image rows must be inserted before
the achievement rows. The merge moved that block; the conflict was the stale
copy left at the old position.

What arrives:

* **Import-thunk recognition** (`imports.rs`, 338 lines). An XEX import is not
  a PLT jump: the linker emits a four-word thunk whose first two words are
  import RECORDS that the loader rewrites at module load. On disc they are
  still records, so a PowerPC-only decoder prints two meaningless `.long`s in
  front of an indirect branch. This maps every word of every thunk, and every
  direct branch into one, back to its `imports` row. Shape-validated rather
  than trusted: an entry is indexed only when the four words it points at
  actually have the thunk shape.

  Adds `instructions.import_address` (FK onto `imports.address`) and
  `import_role` (`'record'` | `'thunk'` | `'call'`, NULL iff import_address is
  NULL), plus an index. `tools/zq.py` gains `imp` and `impcalls`, and `dis`
  now names imports instead of printing `.long 0x01010194`.

* **Sixteen schema-wide foreign keys**, declared wherever a column is derived
  from another table. CREATE TABLE and insertion order become load-bearing.

  `functions` is deliberately NOT an FK parent and the golden test now asserts
  zero inbound FKs onto it: DuckDB implements UPDATE as delete+insert, so one
  inbound FK would make `functions.name` un-updatable and break
  `apply_re_symbols.sql`, which re-applies RE symbol names after every
  regeneration.

The database path needed no change in the binary: `DbWriter` builds its own
index inside `ingest_instructions` from `info.import_libraries`. Only the two
output paths (`enrich_section` for JSONL, `write_asm`) take it as an argument.

Verified: `cargo test -p sylpheed-xexdb` = 10 passed / 0 failed, including
`db_schema_golden` (41s, builds a real DuckDB) which locks the 16-FK set and
the no-FK-onto-functions rule. `cargo clippy -p sylpheed-xexdb --all-targets
-- -D warnings` clean; `cargo fmt --all --check` clean.

Stacked on #32 -- it ports into a crate that only exists there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 20:43:16 +02:00
..
2026-09-13 19:31:49 +02:00

sylph-xexdb — the title's XEX, as a queryable database

sylph-xexdb dis <disc.iso|default.xex> --db sylpheed.db --analyze sql
tools/zq.py dis 0x82341a20 0x82341b00      # then query it

Extract the PE image, disassemble it, detect functions, resolve cross-references and RTTI, and write the lot to DuckDB.

Where it came from, and what did not come with it

This was xenia-rs — a from-scratch Rust Xbox 360 emulator, started because Canary would not run on Linux. Canary later did, with fixes from us, and the emulator was retired. The static analysis is the part that stayed useful, so it came here and the emulator did not: no interpreter, no JIT, no scheduler, no GPU, no kernel. About 17,000 lines of ~71,000.

That was possible because the coupling was three symbols — decoder::decode, disasm::DisasmItem, disasm::format — now sylpheed-ppc.

crate what it is
sylpheed-xex the XEX2 container: decrypt, LZX, PE image, resources, and the disc image it may live in
sylpheed-ppc PowerPC decode and disassembly
sylpheed-xexdb the analysis passes, the schema, and this binary

⚠️ Two things that will mislead you

The database is DuckDB. xenia-rs's own --db help said "SQLite" in two places and was wrong. Query it with python3 -c "import duckdb", or zq.py.

indirect_dispatch_candidates is deliberately not a cross product. A bcctrl through this->vptr at offset 0 matches nearly every class, so one site can claim 700+ callees. Sites past --max-indirect-candidates record a truthful candidate_count with truncated set and emit no candidate rows. On this title that is 6,556 of 6,983 sites, standing for 1,801,075 candidates that are counted rather than materialised. An older database built without the ceiling has ~1.8M more rows in that table and in xrefs, and they say nothing extra.

The export table

Import names come from docs/reference/xbox360-exports.json — 2,913 ordinals, compiled in by build.rs.

🔴 It used to come from Canary's xboxkrnl_table.inc through a relative path to a sibling checkout, which printed a warning and produced an empty table whenever that checkout was not there. Every import in the database then resolved to nothing, and the build still succeeded. Now the source is in this repository and a missing file fails the build.