The row still described ixud.rs as a cue reader and said nothing about the record table or the caption families. Rewritten with the measured numbers: * the IXUD record/field table is decoded and wired in -- IxudObject mirrors IdxdObject, uniform 16-byte records, 12-byte fields, every offset in CHARS, and the word at 0x08 is record 0's hash rather than a schema id. Verified 1104/1104 objects, 1476/1476 records, 628165/628165 named fields. * caption text goes 537 -> 8800 lines, which is 8800 of 8800 distinct keys, in two steps: generalising the key parser to all eight families took it to 8074, and switching from token adjacency to record fields finished it. The row also carries my correction rather than quietly dropping it: the earlier "1.3% of the game's text" counted occurrences across blocks, and the honest denominator is 8800 distinct keys, so the starting point was 6.1%. And it keeps the DEMO control, which is the part a reader should remember: token adjacency finds 537 lines in that family, fields find 541. The old reader was dropping lines in the one family it was written for. Status moved from 🟡/✅ to ✅. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
Project Sylpheed: Arc of Deception — Reborn
A clean-room, open-source reimplementation of Project Sylpheed: Arc of Deception (Xbox 360, 2006).
Built with Rust and the Bevy game engine. Runs natively on Windows, macOS, and Linux, and in the browser via WebAssembly.
Legal note: This project contains no original game code or assets. You must own a legitimate copy of Project Sylpheed to use this engine. Assets remain the intellectual property of SETA Corporation / Square Enix.
Current Status: Milestone 1 — Asset Explorer
- XISO disc image reading via
xdvdfs - Xbox 360 texture de-tiling (Morton / Z-order)
- XPR2 texture container parsing
- Bevy custom
AssetLoaderfor.xprtextures - Orbit camera viewer
- CLI tools: extract, list, sniff, texture info, texture export
- GitHub Actions CI (Windows + macOS + Linux + WASM)
- WASM / web build target
- Mesh format (reverse engineering in progress)
- Audio format (XMA → PCM pipeline)
- Mission data format
Repository Structure
sylpheed-reborn/
└── sylpheed-viewer/ ← Milestone 1: asset explorer (Rust/Bevy workspace)
├── Cargo.toml ← Workspace root
├── crates/
│ ├── sylpheed-formats/ # Format parsers — no Bevy dependency
│ │ ├── xiso.rs # XISO / XDVDFS disc image reader
│ │ ├── texture.rs # XPR2 container + DXT de-tiling (Morton)
│ │ ├── vfs.rs # Virtual filesystem + magic-byte sniffer
│ │ ├── mesh.rs # Mesh parser (stub — RE in progress)
│ │ └── audio.rs # Audio parser (stub — XMA TODO)
│ │
│ ├── sylpheed-viewer/ # Bevy application
│ │ ├── lib.rs # App setup + WASM entry point
│ │ ├── main.rs # Native binary entry point
│ │ ├── asset_loader.rs # Custom Bevy AssetLoaders
│ │ ├── camera.rs # Orbit camera (LMB orbit, RMB pan, scroll zoom)
│ │ └── ui.rs # egui file browser + RE notes panel
│ │
│ └── sylpheed-cli/ # Command-line tools
│ └── main.rs # extract / list / sniff / texture info commands
│
├── assets/ ← Extracted game files (gitignored)
├── .github/workflows/
│ └── ci.yml # CI: Windows + macOS + Linux + WASM
├── Trunk.toml # WASM build configuration
└── justfile # All build recipes
Getting Started
Prerequisites
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install just (task runner)
cargo install just
# For web builds only
cargo install trunk
rustup target add wasm32-unknown-unknown
Linux — Bevy system dependencies
sudo apt-get install -y \
libasound2-dev libudev-dev libwayland-dev \
libxkbcommon-dev libx11-dev libxi-dev pkg-config
Step 1 — Extract your disc
cd sylpheed-viewer
# Extract using the CLI tool
just extract /path/to/project_sylpheed.iso
# Or manually with xdvdfs
cargo install xdvdfs-cli
xdvdfs unpack project_sylpheed.iso ./assets/
Step 2 — Run the viewer
cd sylpheed-viewer
just run # Native viewer
just web # WASM dev server → http://localhost:8080
The asset viewer opens an orbit camera scene. Use the left panel to browse extracted game files. Click a .xpr file to preview it as a texture.
Build Commands
All commands run from the sylpheed-viewer/ directory.
just run # Run the native viewer (debug)
just run-dev # Run with hot-reloading
just build-native # Build native release binary
just web # WASM dev server at localhost:8080
just build-web # WASM release build → ./dist/
just extract game.iso # Extract an ISO to ./assets/
just sniff # Identify file formats in ./assets/
just sniff-unknown # Show only unrecognised formats (RE focus)
just test # Run all tests
just ci # Full CI: fmt + lint + test + WASM check
Technology Stack
| Layer | Choice | Reason |
|---|---|---|
| Language | Rust | Memory safety, performance, cross-platform |
| Game engine | Bevy 0.15 | ECS-first, WASM-native, data-driven |
| XISO reading | xdvdfs | Pure Rust, reads Xbox 360 disc images |
| Binary parsing | binrw | Derive-macro based, ideal for RE work |
| Web bundler | Trunk | Bevy's standard WASM build tool |
| CLI | clap | Asset extraction and inspection tools |
| Debug UI | bevy_egui | In-viewer asset browser and RE notes panel |
Key Architectural Decisions
sylpheed-formats has zero Bevy dependency. All binary format parsers live here and are testable with plain cargo test. Bevy integration is a thin layer on top in sylpheed-viewer.
WASM is a first-class target. XISO reading is gated behind #[cfg(not(target_arch = "wasm32"))] since the browser can't read local files. On the web, assets must be pre-extracted and served over HTTP.
Modding is designed in from the start. The virtual filesystem (vfs.rs) is the single choke point for all asset reads — a mod loader only needs to intercept that one layer to override any file.
Reverse Engineering Notes
Known file formats
| Path pattern | Format | Status | Notes |
|---|---|---|---|
*.XPR, *.XPR2 |
XPR2 texture | ✅ Parsing | DXT1/3/5, DXN, Morton de-tiling done |
DEFAULT.XEX |
Xbox EXE 2 | 🔬 Study | Main executable — load in Ghidra (PPC BE) |
*.XWB, *.XSB |
XACT audio | ⏳ TODO | Wave/sound banks; audio is XMA codec |
*.pak, *.p00 |
SETA archive | ⏳ Unknown | Paired header/data format |
| mesh files | Unknown | ⏳ TODO | Run just sniff-unknown to find candidates |
RE workflow
# 1. Extract and map the disc
just extract game.iso
just sniff-unknown # shows hex magic bytes of unknown files
# 2. Hex-inspect candidates
# Groups of 12 bytes at offsets: likely f32 XYZ vertices
# Groups of 6 bytes: likely u16 triangle indices
# 3. Cross-reference in Ghidra
# Load DEFAULT.XEX with PowerPC Big-Endian processor
# Search string refs to file extensions → find load functions
# 4. Implement parser
# Add binrw #[derive(BinRead)] struct in sylpheed-formats/src/mesh.rs
# cargo test -p sylpheed-formats
Next steps (Milestone 2)
- Mesh format — fingerprint with
sniff-unknown, implementmesh.rs - Audio — parse XWB headers, batch-convert XMA to WAV via
ffmpeg - Mission data — format unknown; starts after mesh/texture loading works
- Flight model — document by playing the original; implement as Bevy system
Constraints
- Never copy decompiled or disassembled game code — reimplement behavior through observation only.
- Assets stay gitignored —
assets/and*.isoare excluded. Never commit game files. - Keep
sylpheed-formatsBevy-free — parsers must be testable without a GPU. - WASM must always compile — CI enforces
cargo check --target wasm32-unknown-unknown. justfileis the source of truth for build commands — add new recipes there.
Milestone Roadmap
| Milestone | Goal | Status |
|---|---|---|
| 1 | Asset Explorer | 🚧 In Progress |
| 2 | Flying Tech Demo | ⏳ Planned |
| 3 | Combat Prototype | ⏳ Planned |
| 4 | Mission 1 Playable | ⏳ Planned |
| 5 | Full Game (all 16 missions) | ⏳ Planned |
| 6 | Mod SDK | ⏳ Planned |
Useful References
- xdvdfs — XISO disc image reader (used in this project)
- Xenia emulator — GPU/API reference (cloned at
../../xenia-canary/) - xboxdevwiki — Xbox 360 hardware documentation
- binrw docs — binary format parsing framework
- Free60 Project — open Xbox 360 hardware docs