From 6dcdea1cf17ca0c37d6321b21d7eca585dda9687 Mon Sep 17 00:00:00 2001 From: sim Date: Wed, 16 Sep 2026 22:19:54 +0200 Subject: [PATCH] docs(reference): adopt the last reference files from the project root Three untracked files in the project root had no home in either repository: - the Xbox 360 technical reference report (a research compilation) becomes docs/reference/xbox360-re-technical-reference.md, unchanged; - XBOX360_ARCHITECTURE.md becomes docs/reference/xbox360-architecture.md, trimmed to its platform facts. Its format "status" sections (PAK unknown, mesh unknown, audio TODO) and the `just sniff` workflow predate every decoder in this repo and were wrong; - generate_export_docs.py becomes tools/generate_export_docs.py, the path the committed xbox360-exports.* already name as their generator. It lived inside a Canary checkout, so it now takes --canary and --out and fails loudly on a tree that is not Canary. Co-Authored-By: Claude Opus 5 (1M context) --- docs/reference/README.md | 6 + docs/reference/xbox360-architecture.md | 115 +++ .../xbox360-re-technical-reference.md | 839 ++++++++++++++++++ tools/generate_export_docs.py | 368 ++++++++ 4 files changed, 1328 insertions(+) create mode 100644 docs/reference/xbox360-architecture.md create mode 100644 docs/reference/xbox360-re-technical-reference.md create mode 100755 tools/generate_export_docs.py diff --git a/docs/reference/README.md b/docs/reference/README.md index 77f44332..4f674a6d 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -13,6 +13,12 @@ repository or as an untracked file on one disk, which is the same as not existin | `xex2-format.md` | the XEX2 container format | `xex2tractor`, byte-identical | | `xbox360-exports.{json,md}` | 2,913 system exports across `xboxkrnl.exe`, `xam.xex`, `xbdm.xex` | `xex2tractor`, byte-identical | | `ppc-instructions.{json,md}` | the PowerPC instruction reference | untracked, project root | +| `xbox360-re-technical-reference.md` | a research compilation on the whole platform: CPU, GPU, XDVDFS, STFS, XEX2, kernel, XMA, security | untracked, project root | +| `xbox360-architecture.md` | a one-page platform quick reference, trimmed to its platform facts | untracked, project root | + +`xbox360-exports.{json,md}` can be regenerated from a Canary checkout with +`python3 tools/generate_export_docs.py` (adopted from the same project root). The +committed copies come from an older Canary, so a fresh run changes a few statuses. `LICENSE` at the repository root came from the same place — MIT, and this repository had none at all. diff --git a/docs/reference/xbox360-architecture.md b/docs/reference/xbox360-architecture.md new file mode 100644 index 00000000..0f6eb860 --- /dev/null +++ b/docs/reference/xbox360-architecture.md @@ -0,0 +1,115 @@ + + +# Xbox 360 Architecture — RE Quick Reference + +A condensed reference for reverse engineering Xbox 360 games. The longer +[technical reference report](xbox360-re-technical-reference.md) has the +binary-level detail. Project Sylpheed's own formats are decoded, and each has a +page under [`docs/re/`](../re/INDEX.md). + +--- + +## CPU: IBM Xenon + +| Property | Value | +|----------|-------| +| Architecture | PowerPC 2.02 (64-bit ISA, 32-bit addressing in games) | +| Cores | 3 symmetric cores × 2 SMT threads = **6 logical processors** | +| Clock | 3.2 GHz | +| Execution | In-order, dual-issue superscalar (no speculation) | +| L1 I/D cache | 32 KB each, per core | +| L2 cache | 1 MB unified, shared across all 3 cores | +| Vector registers | **VMX-128** — 128 × 128-bit VRs per thread (vs. standard AltiVec's 32) | +| Endianness | **Big-endian** | + +**Key for RE:** All multi-byte integer and float values in game binaries are big-endian. + +The VMX-128 extension adds dot-product instructions (`vmsum3fp128`/`vmsum4fp128`) +and D3D pack/unpack instructions absent from standard PowerPC. Their semantics are in +the [PowerPC instruction manual](../../tools/ppc-manual/README.md). + +--- + +## GPU: Xenos (ATI R400/R500 derived) + +| Property | Value | +|----------|-------| +| Clock | 500 MHz | +| Shader processors | 48 unified (3 SIMD × 16, each with 5-wide VLIW ALU) | +| Peak throughput | ~240 GFLOPS | +| On-chip memory | **10 MB eDRAM** (framebuffer, depth, MSAA — no separate VRAM) | +| System RAM | 512 MB GDDR3 shared with CPU | +| Northbridge | GPU also acts as northbridge / memory controller | + +### Command Packets (PM4) + +The GPU is driven by a **PM4 command ring buffer** written by the CPU. Key packet types: + +| Type | Description | +|------|-------------| +| `DRAW_INDX` | Indexed draw call | +| `SET_CONSTANT` | Write shader constant registers | +| `SET_TEXTURE_CONTROL` | Texture fetch constant (address, format, tiling mode) | +| `LOAD_ALU_CONSTANT` | Load vertex shader constants | + +Xenia Canary's `src/xenia/gpu/` implements a full PM4 parser — invaluable for +understanding draw calls and state management. + +### Texture Tiling + +Xbox 360 textures are stored **tiled** in memory, not linear; the Xenos hardware +de-tiles on fetch. Texture data read from game files must be de-tiled before it is +displayed. Tile size depends on the texture format (DXT1 = 4×4 blocks, etc.). + +--- + +## Executable Format: XEX2 + +`default.xex` is the main game executable in Microsoft's **Xbox EXE (XEX2)** format. + +| Offset | Field | Notes | +|--------|-------|-------| +| 0x00 | Magic | `58455832` = `XEX2` | +| 0x04 | Module flags | Security, format flags | +| 0x08 | Header data offset | Points to optional header list | +| 0x10 | Security info offset | RSA signature, image hash | + +The executable contains encrypted, compressed PowerPC code. The full layout is in +[`xex2-format.md`](xex2-format.md). This project loads and disassembles it with +`crates/sylpheed-xex` and `crates/sylpheed-xexdb`. + +--- + +## Platform File Formats + +### XPR2 — Texture Container + +| Offset | Field | Value | +|--------|-------|-------| +| 0x00 | Magic | `58505232` = `XPR2` | +| 0x04 | Total size | u32 BE | +| 0x08 | Header size | u32 BE — data starts at this offset | +| 0x0C | Object count | u32 BE | + +Each texture object contains a D3DFORMAT, width/height/depth/mip levels, and tiled +pixel data. + +### XWB / XSB — XACT Audio Banks + +Wave banks (`.xwb`) and sound banks (`.xsb`) are part of Microsoft's **XACT audio +system**. Audio data is encoded as **XMA** (Xbox Media Audio), a proprietary +Microsoft codec based on WMA Pro. + +--- + +## Useful External References + +- [xboxdevwiki.net](https://xboxdevwiki.net) — Community Xbox 360 hardware documentation +- [Xenia Canary source](https://github.com/xenia-canary/xenia-canary) — GPU/kernel RE reference +- [xdvdfs](https://github.com/antangelo/xdvdfs) — Pure Rust XISO reader (a dependency of `sylpheed-formats`) +- [binrw](https://binrw.rs) — Derive-macro binary format parsing (a dependency of `sylpheed-formats`) +- [Free60 Project](https://free60project.github.io/wiki/) — Original Xbox 360 open-source hardware documentation diff --git a/docs/reference/xbox360-re-technical-reference.md b/docs/reference/xbox360-re-technical-reference.md new file mode 100644 index 00000000..0e4f3525 --- /dev/null +++ b/docs/reference/xbox360-re-technical-reference.md @@ -0,0 +1,839 @@ + + +# Reverse Engineering Xbox 360: A Complete Technical Reference + +**The Xbox 360's architecture — a custom PowerPC CPU, unified-shader GPU, and proprietary OS stack — presents one of the most complex reverse engineering targets in console history.** This document provides a definitive binary-level reference for every major subsystem relevant to game porting, from raw instruction encodings and GPU command packets to file format structs and kernel internals. Every offset, opcode, and constant documented here derives from the Xenia emulator source code, Free60 project research, community reverse engineering, and leaked SDK analysis. All multi-byte fields in Xbox 360 structures are **big-endian** unless explicitly noted otherwise. + +--- + +## 1. IBM Xenon CPU: three in-order PowerPC cores at 3.2 GHz + +The Xenon (codenamed "Waternoose") is a custom IBM chip implementing the **PowerPC 2.02 ISA** in 64-bit mode, though games run predominantly in 32-bit addressing. Three symmetric cores share a single die, each running **2 hardware threads (SMT)** for 6 total logical processors. All cores execute **in-order** with dual-issue superscalar pipelines — no out-of-order speculation. The chip clocks at **3.2 GHz** and connects to the Xenos GPU via a **21.6 GB/s front-side bus** (10.8 GB/s per direction). + +### Cache hierarchy + +| Level | Size | Associativity | Line Size | Notes | +|-------|------|---------------|-----------|-------| +| L1 I-Cache | 32 KB / core | 2-way | 128 bytes | Private per core | +| L1 D-Cache | 32 KB / core | 4-way | 128 bytes | **Write-through, no write-allocate** — writes bypass L1, go to L2 | +| L2 Cache | 1 MB unified | 8-way | 128 bytes | Shared across all 3 cores, 1.6 GHz, 256-bit bus (51.2 GB/s) | + +The L1 D-cache's no-write-allocate policy means streaming writes pass directly to L2, preventing cache thrashing for write-heavy workloads. A custom **xDCBT** prefetch instruction bypasses L2 entirely, pulling data into L1 directly. The GPU can **lock L2 lines** for Xbox Procedural Synthesis (XPS), where the CPU generates geometry into locked cache sets that the GPU reads without a main-memory round-trip. + +### Register file + +Each hardware thread maintains its own independent context — critical for understanding disassembly: + +| Register Set | Count × Width | SPR Number | Purpose | +|-------------|---------------|------------|---------| +| GPR r0–r31 | 32 × 64-bit | — | General purpose | +| FPR f0–f31 | 32 × 64-bit | — | IEEE 754 double-precision floating point | +| VR v0–v127 | **128 × 128-bit** | — | VMX-128 vector registers (extended from standard 32) | +| CR | 1 × 32-bit (8 × 4-bit fields) | — | Condition register (CR0–CR7) | +| LR | 1 × 64-bit | SPR 8 | Link register (return address) | +| CTR | 1 × 64-bit | SPR 9 | Count register / branch target | +| XER | 1 × 64-bit | SPR 1 | Fixed-point exception (carry, overflow) | +| FPSCR | 1 × 32-bit | — | FP status and control | +| TBL / TBU | 1 × 32-bit each | SPR 268/269 | Time base lower/upper | +| PURR | 1 × 64-bit | SPR 309 | Per-thread cycle counter | + +The **VMX-128 extension** is the most significant Xenon-specific ISA change. Standard AltiVec provides 32 vector registers (v0–v31) with 5-bit encoding; Xenon extends this to **128 registers per thread** using 7-bit encoding, for **256 × 128-bit registers per core**. Xenon adds single-cycle `vdot3`/`vdot4` dot-product instructions and D3D compressed-format handling instructions absent from standard AltiVec. + +### Key instruction encodings + +**Atomic operations (load-linked / store-conditional):** +``` +lwarx RT, RA, RB — Load Word And Reserve Indexed + Encoding: 0x7C000028 | (RT<<21) | (RA<<16) | (RB<<11) + [31:26]=0b011111 [25:21]=RT [20:16]=RA [15:11]=RB [10:1]=0000010100 [0]=0 + +stwcx. RS, RA, RB — Store Word Conditional Indexed (always records to CR0) + Encoding: 0x7C00012D | (RS<<21) | (RA<<16) | (RB<<11) + [31:26]=0b011111 [25:21]=RS [20:16]=RA [15:11]=RB [10:1]=0010010110 [0]=1 +``` + +**VMX load/store (128-bit aligned):** +``` +lvx VRT, RA, RB — Load Vector Indexed + Primary=31, XO=103. Encoding: 0x7C0000CE | (VRT<<21) | (RA<<16) | (RB<<11) + +stvx VRS, RA, RB — Store Vector Indexed + Primary=31, XO=231. Encoding: 0x7C0001CE | (VRS<<21) | (RA<<16) | (RB<<11) +``` + +**Floating-point special:** +``` +fsel FRT, FRA, FRC, FRB — Floating Select (FRT = FRA >= 0 ? FRC : FRB) + Primary=63, XO=23. Encoding: 0xFC00002E | (FRT<<21) | (FRA<<16) | (FRB<<11) | (FRC<<6) + +frsqrte FRT, FRB — Floating Reciprocal Square Root Estimate + Primary=63, XO=26. Encoding: 0xFC000034 | (FRT<<21) | (FRB<<11) +``` + +**Synchronization barriers:** +``` +hwsync (sync L=0): 0x7C0004AC — Full memory barrier, drains write buffers +lwsync (sync L=1): 0x7C2004AC — Lightweight barrier, orders loads/stores +eieio: 0x7C0006AC — Enforces I/O ordering for MMIO +``` + +### Thread scheduling and hardware priority + +Games pin work to specific hardware threads (0–5) via `XSetThreadProcessor()`. Thread 0 on Core 0 is the master thread. The hypervisor controls thread dispatch; hardware thread priority is managed through HID (Hypervisor Implementation-Dependent) registers. + +### Physical address space memory map + +| Address Range | Size | Description | +|--------------|------|-------------| +| `0x00000000`–`0x1FFFFFFF` | 512 MB | Main GDDR3 DRAM (unified CPU/GPU) | +| `0x7FEA0000` | ~64 KB | **XMA audio decoder** MMIO registers | +| `0xC0000000`–`0xC00FFFFF` | ~1 MB | PCI / Southbridge configuration | +| `0xC8000000` | Region | NAND flash controller | +| `0xE0000000`–`0xEFFFFFFF` | Region | GPU interface / bus region | +| `0xEC800000` | Large region | **Xenos GPU register file** (MMIO) | + +Internally, the CPU SoC contains a **32 KB boot ROM**, **64 KB secure SRAM**, **768 eFuse bits** for per-console keys, and a hardware security engine — all accessible only at HV privilege. + +--- + +## 2. Xenos GPU: unified shaders, 10 MB eDRAM, and PM4 command packets + +The Xenos is a custom ATI design based on the R400/R500 family — the **world's first GPU with unified shader architecture**, predating even ATI's own R600. It clocks at **500 MHz** with **48 unified shader processors** in 3 SIMD arrays of 16, each with a **5-wide VLIW ALU** (240 ALUs total, **240 GFLOPS peak**). It also serves as the system northbridge, hosting both **64-bit dual memory controllers** (22.4 GB/s to 512 MB GDDR3) and the CPU FSB interface. + +### eDRAM: the 10 MB render target cache + +A separate daughter die containing **10 MB of embedded DRAM** sits in the GPU package, connected at **32 GB/s** externally but **256 GB/s** between its internal logic and memory cells. This 8:1 bandwidth ratio makes alpha blending, Z/stencil testing, and **4× MSAA essentially free** — all handled by dedicated logic on the eDRAM die without touching the main bus. + +**Tile geometry from Xenia (`xenos.h`):** +``` +kEdramTileWidthSamples = 80 +kEdramTileHeightSamples = 16 +kEdramTileCount = 2048 +Total = 2048 tiles × 80 × 16 × 4 bytes/sample = 10,485,760 bytes (10 MB) +``` + +Each tile covers **80 × 16 samples** at 4 bytes per sample = 5,120 bytes. A full 720p (1280×720) frame with 32-bit color + 32-bit depth requires ~7.2 MB, fitting comfortably in eDRAM without tiling. With 4× MSAA at 720p, games must use **predicated tiling** — the GPU replays the command buffer per tile, with hardware predication culling off-tile primitives. The `PM4_SET_BIN_MASK` (0x50) and `PM4_SET_BIN_BASE_OFFSET` (0x4B) commands configure this. + +### PM4 command buffer protocol + +The GPU consumes commands from a ring buffer in system memory using the **PM4 (Packet Manager 4)** format inherited from ATI R-series GPUs. Three packet types exist: + +**Type-0 (Register Write):** +``` +Bits [31:30] = 0b00 +Bit [29] = ONE_REG_WR (0=sequential, 1=repeated) +Bits [28:16] = COUNT - 1 +Bits [15:0] = BASE_INDEX (register DWORD index) +Payload: COUNT DWORDs written to sequential registers starting at BASE_INDEX. +``` + +**Type-2 (NOP):** +``` +Bits [31:30] = 0b10 +Bits [29:0] = don't care (padding/alignment filler) +Constant: 0x80000000 +``` + +**Type-3 (IT Command):** +``` +Bits [31:30] = 0b11 +Bit [29] = PREDICATE (conditional on VIZ query) +Bits [28:16] = COUNT - 1 +Bits [15:8] = IT_OPCODE (8-bit) +Bits [7:0] = Reserved +Payload: COUNT DWORDs of command-specific data. +``` + +### PM4 opcode table (from Xenia `xenos.h`) + +| Opcode | Hex | Command | +|--------|-----|---------| +| PM4_NOP | 0x10 | No operation | +| PM4_REG_RMW | 0x21 | Register read-modify-write | +| PM4_DRAW_INDX | 0x22 | Draw indexed primitives | +| PM4_IM_LOAD | 0x27 | Load shader microcode from memory address | +| PM4_IM_LOAD_IMMEDIATE | 0x2B | Load shader microcode inline in packet | +| PM4_IM_STORE | 0x2C | Store shader microcode to system memory | +| PM4_SET_CONSTANT | 0x2D | Set GPU constant registers | +| PM4_LOAD_CONSTANT_CONTEXT | 0x2E | Load constants from memory | +| PM4_DRAW_INDX_2 | 0x36 | Draw with inline index buffer | +| PM4_INDIRECT_BUFFER_PFD | 0x37 | Indirect buffer, pipelined | +| PM4_INVALIDATE_STATE | 0x3B | Selective state invalidation | +| PM4_WAIT_REG_MEM | 0x3C | Wait until register/memory matches value | +| PM4_MEM_WRITE | 0x3D | Write value to memory | +| PM4_REG_TO_MEM | 0x3E | Copy register to memory | +| PM4_INDIRECT_BUFFER | 0x3F | Indirect buffer dispatch | +| PM4_COND_WRITE | 0x45 | Conditional write | +| PM4_EVENT_WRITE | 0x46 | Generate event + memory write on completion | +| PM4_ME_INIT | 0x48 | Microengine initialization | +| PM4_SET_BIN_BASE_OFFSET | 0x4B | Tiled rendering bin offset | +| PM4_SET_BIN_MASK | 0x50 | 64-bit bin mask for tiling | +| PM4_SET_BIN_SELECT | 0x51 | 64-bit bin select | +| PM4_WAIT_REG_EQ | 0x52 | Wait until register == value | +| PM4_WAIT_REG_GTE | 0x53 | Wait until register ≥ value | +| PM4_INTERRUPT | 0x54 | Generate interrupt from command stream | +| PM4_SET_CONSTANT2 | 0x55 | Set constants block 2 | +| PM4_EVENT_WRITE_SHD | 0x58 | VS/PS done event | +| PM4_EVENT_WRITE_CFL | 0x59 | Cache flush done event | +| PM4_EVENT_WRITE_EXT | 0x5A | Screen extent event | +| PM4_EVENT_WRITE_ZPD | 0x5B | Z-pass done event | +| PM4_CONTEXT_UPDATE | 0x5E | Update current context | +| PM4_XE_SWAP | 0x64 | Frame swap (Xenia-specific trigger) | + +### GPU register file layout + +The Xenos register file is MMIO-mapped at physical **0xEC800000**. Registers are addressed by DWORD index (multiply by 4 for byte offset). Shader constant loading dispatches by type: + +| Type Code | Register Base | Contents | +|-----------|--------------|----------| +| 0 | 0x4000 | Float shader constants (256 × float4) | +| 1 | 0x4800 | Fetch constants (texture/vertex descriptors, 24 bytes each) | +| 2 | 0x4900 | Boolean shader constants | +| 3 | 0x4908 | Loop constants | +| 4 | 0x2000 | General GPU configuration registers | + +Key configuration registers include `RBBM_STATUS`, `COHER_STATUS_HOST` (0x0578), `RB_COLOR_INFO` / `RB_DEPTH_INFO` (0x0D00 range), `PA_SC_WINDOW_OFFSET` (0x2000 range), `VGT_DRAW_INITIATOR` (0x2100 range), and `SQ_PROGRAM_CNTL` (0x4000 range). + +### Shader microcode format + +Xenos microcode closely follows the **AMD R600 ISA** (publicly documented). Three instruction types exist: + +- **Control Flow (CF):** 64 bits. Controls execution flow, initiates fetch/export operations. +- **ALU:** 64 bits. 5-slot VLIW (vector + scalar). Grouped into ALU clauses. +- **Fetch:** 96 bits (3 DWORDs). Vertex and texture fetch operations. + +Shader programs are unified — the same microcode format serves both vertex and pixel shaders. The hardware dynamically assigns processors to VS/PS workloads. Xenia's `src/xenia/gpu/ucode.h` defines the complete microcode structures; `shader_translator.cc` translates them to host GPU IR. + +### Memory export (MEMEXPORT) + +**MEMEXPORT** is Xenos-specific: shaders write arbitrary data to system memory addresses, enabling **GPGPU compute** before compute shaders existed. Vertex shaders export computed data (particles, physics, procedural geometry) to memory via special export registers. The CPU can read this data after GPU synchronization. + +### Texture formats + +Xenos supports standard D3D9 formats plus Xbox 360 extensions. Textures use **tiled (Morton/Z-order swizzled)** layout by default for optimal cache performance; linear layout requires explicit specification. Maximum dimensions are **8192×8192** (2D). + +Xbox 360–only compressed formats include **DXT3A** (4×4, 64 bits, scalar), **DXT5A** (4×4, 64 bits, two-endpoint scalar), **DXN** (128 bits, two-channel DXT5A), and **CTX1** (4×4, 64 bits, shared-index). Color render target formats range from `k_8_8_8_8` through `k_2_10_10_10_FLOAT` (7e3 HDR) to `k_32_32_FLOAT`. + +--- + +## 3. XDVDFS: the Xbox DVD file system on game discs + +Xbox 360 game discs use a proprietary file system called **XDVDFS** (also GDFX or XISO) — not ISO 9660 or UDF. The sector size is **2048 bytes (0x800)**. Directory entries form a **binary search tree** rather than a linear listing. + +### Volume descriptor at sector 32 + +The volume descriptor occupies sectors 32–33. On a raw XISO image, it begins at byte offset **0x10000** from the game partition start: + +| Offset | Size | Type | Content | +|--------|------|------|---------| +| 0x000 | 0x14 | char[20] | Magic: `MICROSOFT*XBOX*MEDIA` | +| 0x014 | 0x04 | uint32_le | Root directory table sector | +| 0x018 | 0x04 | uint32_le | Root directory table size (bytes) | +| 0x01C | 0x08 | uint64_le | Timestamp (Win32 FILETIME) | +| 0x024 | 0x7C8 | — | Zero padding | +| 0x7EC | 0x14 | char[20] | Magic (repeated): `MICROSOFT*XBOX*MEDIA` | + +### Directory entry structure (variable size, minimum 14 bytes) + +| Offset | Size | Type | Field | +|--------|------|------|-------| +| 0x00 | 0x02 | uint16_le | Left subtree offset (in DWORDs from directory table start) | +| 0x02 | 0x02 | uint16_le | Right subtree offset (in DWORDs) | +| 0x04 | 0x04 | uint32_le | Starting sector of file data | +| 0x08 | 0x04 | uint32_le | File size in bytes | +| 0x0C | 0x01 | uint8 | Attributes (0x10=DIRECTORY, 0x20=ARCHIVE, 0x80=NORMAL) | +| 0x0D | 0x01 | uint8 | Filename length | +| 0x0E | var | char[] | Filename (padded to DWORD boundary with 0xFF) | + +Subtree offsets of 0 indicate no child. Files occupy contiguous sectors. Subdirectories point to nested directory tables. + +### Game disc geometry (XGD2 / XGD3) + +| Format | Game Partition Offset | Capacity | +|--------|----------------------|----------| +| XGD2 (early 360) | **0x0FD90000** | ~6.8 GiB | +| XGD3 (2011+) | **0x02080000** | ~7.8 GiB | + +All Xbox 360 discs are dual-layer DVD-DL. Security sectors on the outer ring use a challenge/response protocol: 23 entries encrypted with **RC4** (key derived by SHA-1 hashing a 44-byte block at security sector offset 1183), verified by RSA signatures. + +--- + +## 4. STFS: secure transacted file system for saves and DLC + +STFS packages contain downloadable content, save games, profiles, and XBLA titles stored on HDD or memory units. The format uses SHA-1 Merkle trees for integrity and RSA signatures for authentication. Block size is **0x1000 (4096 bytes)**, with hash tables interleaved every **0xAA (170)** data blocks. + +### Header layout (canonical offsets from Free60) + +**Magic bytes at offset 0x000 (4 bytes):** +| Magic | ASCII | Meaning | +|-------|-------|---------| +| 0x434F4E20 | `CON ` | Console-signed (saves, profiles) | +| 0x4C495645 | `LIVE` | Xbox Live Marketplace content | +| 0x50495253 | `PIRS` | Microsoft-signed read-only content | + +**Metadata section:** + +| Offset | Size | Field | +|--------|------|-------| +| 0x022C | 0x100 | License entries (16 × 0x10 bytes: int64 licenseID + int32 bits + int32 flags) | +| 0x032C | 0x14 | Header SHA-1 hash (covers 0x0344 through first hash table) | +| 0x0340 | 0x04 | Header size (uint32) | +| 0x0344 | 0x04 | **Content type** (uint32, see enum below) | +| 0x0348 | 0x04 | Metadata version | +| 0x034C | 0x08 | Content size (uint64) | +| 0x0354 | 0x04 | Media ID | +| 0x0358 | 0x04 | Version | +| 0x035C | 0x04 | Base version | +| 0x0360 | 0x04 | **Title ID** (e.g., 0x4D5307E6 for Halo 3) | +| 0x0364 | 0x01 | Platform (2=Xbox 360, 4=PC) | +| 0x0365 | 0x01 | Executable type | +| 0x0366 | 0x01 | Disc number | +| 0x0367 | 0x01 | Disc in set | +| 0x0368 | 0x04 | Save game ID | +| 0x036C | 0x05 | Console ID | +| 0x0371 | 0x08 | Profile ID | +| 0x0379 | 0x24 | **STFS Volume Descriptor** (36 bytes, see below) | +| 0x039D | 0x04 | Data file count | +| 0x03A1 | 0x08 | Data file combined size | +| 0x0411 | 0x900 | Display name (UTF-8, 18 locales × 0x80 bytes) | +| 0x0D11 | 0x900 | Display description | +| 0x1691 | 0x80 | Title name | +| 0x171A | 0x4000 | Thumbnail image (PNG, ≤16384 bytes) | +| 0x571A | 0x4000 | Title thumbnail image | + +**Total header size (v1): 0x971A bytes.** + +### STFS volume descriptor (36 bytes at offset 0x0379) + +| Offset | Size | Field | +|--------|------|-------| +| 0x00 | 0x01 | Size (always 0x24) | +| 0x01 | 0x01 | Reserved | +| 0x02 | 0x01 | Block separation (bit 0 determines hash table spacing) | +| 0x03 | 0x02 | File table block count | +| 0x05 | 0x03 | File table block number (int24) | +| 0x08 | 0x14 | Top hash table hash (SHA-1) | +| 0x1C | 0x04 | Total allocated block count | +| 0x20 | 0x04 | Total unallocated block count | + +### Hash table hierarchy + +Each **hash entry is 24 bytes (0x18):** 20-byte SHA-1 digest + 1-byte status + 3-byte next-block number. Status values: **0x00** (unused), **0x40** (freed), **0x80** (used), **0xC0** (newly allocated). + +- **Level 0:** 0xAA (170) entries, each covering one data block +- **Level 1:** 0xAA entries, each covering one L0 table — spans 170² = **28,900 blocks** +- **Level 2:** 0xAA entries, each covering one L1 table — spans 170³ = **4,913,000 blocks** + +CON packages may maintain **dual hash tables** per level for transactional integrity. Block-to-file-offset conversion must account for interleaved hash table blocks: +``` +FileOffset(block) = ((headerSize + 0xFFF) & 0xF000) + (adjustedBlock << 12) +``` +where `adjustedBlock` adds 1 hash-table block per 0xAA data blocks, plus L1/L2 table blocks for larger packages. + +### Content type enum (key values) + +| Value | Type | +|-------|------| +| 0x0000001 | Saved Game | +| 0x0001000 | Xbox 360 Title | +| 0x0004000 | Installed Game | +| 0x0007000 | Game on Demand | +| 0x0010000 | Profile | +| 0x0040000 | Cache File | +| 0x0080000 | Game Demo | +| 0x00D0000 | Arcade Title (XBLA) | +| 0x2000000 | Community Game | + +### File listing entry (0x40 = 64 bytes each) + +| Offset | Size | Field | +|--------|------|-------| +| 0x00 | 0x28 | Filename (null-padded, 40 chars max) | +| 0x28 | 0x01 | Flags: bits 0–5 = name length, bit 6 = consecutive blocks, bit 7 = is directory | +| 0x29 | 0x03 | Allocated block count (int24 LE) | +| 0x2C | 0x03 | Allocated block count copy | +| 0x2F | 0x03 | Starting block number (int24 LE) | +| 0x32 | 0x02 | Path indicator (0xFFFF = root; else parent entry index) | +| 0x34 | 0x04 | File size (uint32 BE) | +| 0x38 | 0x04 | Update timestamp (FAT format) | +| 0x3C | 0x04 | Access timestamp (FAT format) | + +--- + +## 5. XEX2: the Xbox executable container format + +XEX2 is an encrypted, compressed wrapper around a standard PE32 image. It is the mandatory packaging format for all Xbox 360 executables. + +### IMAGE_XEX_HEADER (0x18 bytes) + +```c +struct IMAGE_XEX_HEADER { + char Magic[4]; // +0x00 "XEX2" = 0x58455832 + uint32_t ModuleFlags; // +0x04 See flag bits + uint32_t SizeOfHeaders; // +0x08 Offset to PE data + uint32_t SizeOfDiscardableHeaders; // +0x0C Reserved + uint32_t SecurityInfo; // +0x10 File offset → XEX_SECURITY_INFO + uint32_t HeaderDirectoryEntryCount; // +0x14 Number of optional headers + // IMAGE_XEX_DIRECTORY_ENTRY[] at +0x18 +}; +``` + +**Module flags (bitfield):** 0x0001=TITLE_PROCESS, 0x0002=TITLE_IMPORTS, 0x0004=DEBUGGER, 0x0008=DLL, 0x0010=PATCH, 0x0020=PATCH_FULL, 0x0040=PATCH_DELTA, 0x0080=USER_MODE. + +**XEX format variants:** + +| Magic | Hex | Minimum Kernel | +|-------|-----|---------------| +| `XEX2` | 0x58455832 | ≥ 1861 (retail) | +| `XEX1` | 0x58455831 | ≥ 1838 (beta) | +| `XEX%` | 0x58455825 | ≥ 1746 | +| `XEX-` | 0x5845582D | ≥ 1640 | +| `XEX0` | 0x58455830 | ≥ 1332 | + +### Optional header directory (at +0x18) + +Each entry is 8 bytes: **uint32 Key** + **uint32 Value**. The low byte of Key encodes the data format: +- `Key & 0xFF == 0x00`: Value is an immediate flag +- `Key & 0xFF == 0x01`: Value is an immediate DWORD +- `Key & 0xFF == 0xFF`: Value is file offset to size-prefixed struct +- Other: Value is file offset; low byte × 4 = struct size + +**Complete optional header key table:** + +| Key ID | Name | Data Type | +|--------|------|-----------| +| 0x000002FF | Section Table | SizedStruct | +| 0x000003FF | File Data Descriptor (compression/encryption) | SizedStruct | +| 0x00000405 | Patch File Base Reference | Fixed 0x14 | +| 0x000005FF | Delta Patch Descriptor | SizedStruct | +| 0x000103FF | **Import Libraries** | SizedStruct | +| 0x00010001 | Original Base Address | ULONG | +| 0x00010100 | **Entry Point** (VA) | Flag | +| 0x00010201 | PE Base Address | ULONG | +| 0x000183FF | Original PE Module Name | String | +| 0x000200FF | Static Libraries / Build Versions | SizedStruct | +| 0x00020104 | TLS Data (16 bytes) | Struct | +| 0x00020200 | Default Stack Size | Flag | +| 0x00020301 | Default FS Cache Size | ULONG | +| 0x00020401 | Default Heap Size | ULONG | +| 0x00030000 | System Flags / Privileges | Flag | +| 0x00040006 | **Execution ID** (Title ID, version — 24 bytes) | Struct | +| 0x00040310 | Game Ratings (64 bytes) | Fixed 0x40 | +| 0x00040404 | LAN Key (16 bytes) | Fixed 0x10 | +| 0x000405FF | Xbox 360 Logo | SizedStruct | +| 0x000406FF | Multi-Disc Media IDs | SizedStruct | +| 0x00E10402 | PE Exports (IMAGE_DATA_DIRECTORY) | Struct | + +### XEX_SECURITY_INFO structure + +Located at the file offset stored in `SecurityInfo` (+0x10): + +```c +struct XEX_SECURITY_INFO { + uint32_t Size; // +0x000 + uint32_t ImageSize; // +0x004 + HV_IMAGE_INFO ImageInfo; // +0x008 (0x174 bytes, see below) + uint32_t AllowedMediaTypes; // +0x17C + uint32_t PageDescriptorCount; // +0x180 + HV_PAGE_INFO PageDescriptors[]; // +0x184 (variable, 0x18 bytes each) +}; +``` + +**HV_IMAGE_INFO (0x174 bytes at SecurityInfo+0x08):** + +| Offset | Size | Field | +|--------|------|-------| +| 0x000 | 0x100 | RSA-2048 signature (256 bytes) | +| 0x100 | 0x04 | Info size | +| 0x104 | 0x04 | Image flags | +| 0x108 | 0x04 | Virtual load address | +| 0x10C | 0x14 | Image SHA-1 hash | +| 0x120 | 0x04 | Import table count | +| 0x124 | 0x14 | Import table SHA-1 digest | +| 0x138 | 0x10 | Media ID (16 bytes) | +| **0x148** | **0x10** | **Encrypted AES-128 session key** | +| 0x158 | 0x04 | Export table VA | +| 0x15C | 0x14 | Header SHA-1 hash | +| 0x170 | 0x04 | Game region flags | + +**HV_PAGE_INFO (0x18 bytes each):** +```c +struct HV_PAGE_INFO { + uint32_t PageDescription; // +0x00 Bits [31:28]=Info(type), [27:0]=Size(page count) + uint8_t DataDigest[0x14]; // +0x04 SHA-1 hash of page data +}; +// Info values: 1=CODE, 2=DATA, 3=READONLY_DATA +``` + +### Execution ID (24 bytes at optional header 0x00040006) + +```c +struct XEX_EXECUTION_ID { + uint32_t MediaID; // +0x00 + uint32_t Version; // +0x04 Major(4):Minor(4):Build(16):QFE(8) + uint32_t BaseVersion; // +0x08 + uint32_t TitleID; // +0x0C e.g. 0x4D5307E6 (Halo 3) + uint8_t Platform; // +0x10 + uint8_t ExecutableType; // +0x11 + uint8_t DiscNum; // +0x12 + uint8_t DiscsInSet; // +0x13 + uint32_t SaveGameID; // +0x14 +}; +``` + +### Encryption keys and decryption flow + +``` +XEX2 Retail Key (AES-128): + 20 B1 85 A5 9D 28 FD C3 40 58 3F BB 08 96 BF 91 + +XEX2 Devkit Key (AES-128): + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (all zeros) +``` + +**Decryption procedure:** +1. Read 16-byte encrypted session key from `HV_IMAGE_INFO.ImageKey` (offset +0x148) +2. Decrypt with **AES-128-ECB** using the retail (or devkit) key → per-file session key +3. Decrypt the PE payload (starting at file offset `SizeOfHeaders`) using **AES-128-CBC** with the session key (IV = all zeros initially) +4. If encrypted flag not set in File Data Descriptor, skip decryption + +### Compression + +The **File Data Descriptor** (key 0x000003FF) specifies compression: + +| Format Value | Name | Method | +|-------------|------|--------| +| 0x0000 | NONE | Uncompressed | +| 0x0001 | RAW | Block-based (DataSize + ZeroSize pairs, 8 bytes each) | +| 0x0002 | COMPRESSED | LZX-based with SHA-1 per block (window typically 0x8000 or 0x20000) | +| 0x0003 | DELTA_COMPRESSED | Patch format | + +### Import system + +Imports are **ordinal-only**. The import descriptor (key 0x000103FF) contains: +```c +struct XEX_IMPORT_DESCRIPTOR { + uint32_t Size; // +0x00 + uint32_t NameTableSize; // +0x04 + uint32_t ModuleCount; // +0x08 + // char NameTable[] (null-separated library names) + // XEX_IMPORT_TABLE[] (one per module) +}; +``` +Each per-library table contains a module index, version info, import count, and an array of thunk virtual addresses. Bit 24 of each import record distinguishes function imports (patched as branch stubs) from variable imports (address written directly). + +--- + +## 6. Kernel and operating system internals + +The Xbox 360 runs a modified NT kernel (`xboxkrnl.exe`) under a **hypervisor (HV)** — roughly 128 KB of code operating in PowerPC real mode with highest privilege. The HV enforces **W^X** (write XOR execute) across all memory, manages page tables via software TLB, and encrypts all executable pages in RAM. Games run in kernel mode with virtual addressing; there is effectively one "process" at a time. + +### Kernel exports (by ordinal) + +Exports are ordinal-only. Selected confirmed ordinals from Xenia's `xboxkrnl_table.inc`: + +| Ordinal | Export | +|---------|--------| +| 0x001 | DbgBreakPoint | +| 0x003 | DbgPrint | +| 0x00E | ExEventObjectType | +| 0x0CF | NtClose | +| 0x0D0 | NtCreateDirectoryObject | +| 0x0D1 | NtCreateEvent | +| 0x0D2 | NtCreateFile | +| 0x0D3 | NtCreateIoCompletion | +| 0x0D4 | NtCreateMutant | +| 0x0D5 | NtCreateSemaphore | +| 0x0D7 | NtCreateTimer | +| 0x0D8 | NtDeleteFile | +| 0x156 | XboxHardwareInfo (variable, 16 bytes) | +| 0x157 | XboxKrnlBaseVersion | +| 0x158 | XboxKrnlVersion | +| 0x159 | XeCryptAesKey | +| 0x15A | XeCryptAesEcb | +| 0x15B | XeCryptAesCbc | +| 0x164 | XeCryptBnQwBeSigCreate | +| 0x166 | XeCryptBnQwBeSigVerify | +| 0x16D | XeCryptBnQwNeRsaPubCrypt | + +The full table contains **~900+ exports**. Additional families include Ex* (executive), Ke* (kernel core), Rtl* (runtime), Mm* (memory manager), Ob* (object manager), Xex* (module loading), and Vd* (video). The authoritative reference is Xenia's `xboxkrnl_table.inc`. + +### Hypervisor syscall mechanism + +The PowerPC `sc` instruction triggers a syscall interrupt. The CPU enters real mode and vectors to **0x00000C00**. Syscall number is in **r0**, arguments in **r3–r12**, return value in **r3**. + +The HV dispatcher (from the 4532 kernel vulnerability disclosure): +```asm +0x13D8: cmplwi %r0, 0x61 ; max syscall = 0x61 (97) +0x13DC: bge illegal_syscall +0x13F0: rldicr %r1, %r0, 2, 61 ; offset = r0 * 4 +0x13F4: lwz %r4, 0x1F68(%r1); load handler from table at 0x1F68 +0x1414: blrl ; call handler +``` + +**Key HV syscalls:** + +| Number | Name | +|--------|------| +| 0x00 | HvxGetVersions | +| 0x02 | HvxMemoryProtect | +| 0x07 | HvxGetSpecialPurposeRegister | +| 0x08 | HvxSetSpecialPurposeRegister | +| 0x10 | HvxDvdAuthBegin | +| 0x16 | HvxXexLoadImage | +| 0x17 | HvxXexUnloadImage | +| 0x19 | HvxXexGetProcAddress | +| 0x1A | HvxXexGetModuleHandle | +| 0x25 | HvxXexVerifyImage | +| 0x40 | HvxKeysInitialize | +| 0x41 | HvxKeysExGetKey | +| 0x45 | HvxKeysExGetConsoleId | +| 0x46 | HvxKeysSetKeyVault | + +### Thread local storage + +**r13** points to the KPCR (Kernel Processor Control Region) in kernel mode. **r2** is the user-mode TLS base pointer. The TEB inherits NT structure with key fields at: +0x02C (ThreadLocalStoragePointer), +0x030 (PEB pointer), +0xE10 (TlsSlots[64]). + +--- + +## 7. Xbox 360 Direct3D: a thin layer over GPU command buffers + +The Xbox 360 D3D implementation is a **bare-bones wrapper** that writes PM4 packets directly into a GPU ring buffer — no abstracted driver stack. Key differences from PC D3D9 include direct memory control via XGraphics (`XGSetTextureHeader`, `XGSetVertexBufferHeader`), eDRAM-centric rendering requiring explicit resolve operations, and extensions like `vfetch`, `tfetch`, and MEMEXPORT. + +### Ring buffer architecture + +The Xenos processes commands from a circular buffer in system memory defined by: +- **Base pointer:** Physical start address of ring buffer +- **Write pointer:** CPU advances after writing PM4 packets +- **Read pointer:** GPU advances as it consumes commands +- **Size:** Total buffer capacity + +The GPU stalls when read would pass write. Secondary command lists dispatch via `PM4_INDIRECT_BUFFER` (0x3F), analogous to D3D12 command lists. + +### Shader constant register mapping + +Float constants load into GPU registers at base **0x4000** (256 × float4 = 1024 DWORDs for VS, extending into PS range). Fetch constants (texture/vertex descriptors) occupy base **0x4800** as 24-byte structures encoding buffer address, stride, format, and dimensions. Boolean constants pack at **0x4900** as 32-bit bitmasks. Loop constants sit at **0x4908**. + +### CPU-to-GPU memory translation + +In the **unified memory architecture**, CPU and GPU share the same 512 MB physical address space. Vertex/index buffer allocation simply returns a CPU-accessible virtual address that maps to the same physical memory the GPU reads. Fetch constants store the **physical address** of the buffer. No upload or copy is needed — the GPU reads directly from where the CPU wrote. + +### Predicated tiling workflow + +When eDRAM cannot hold the full framebuffer (e.g., 4× MSAA at 720p), D3D splits rendering into tiles. For each tile, the GPU **replays the entire command buffer** with hardware predication culling off-tile primitives. This is configured via `PM4_SET_BIN_BASE_OFFSET` (0x4B), `PM4_SET_BIN_MASK` (0x50), and `PM4_SET_BIN_SELECT` (0x51). After rendering each tile, a resolve operation copies eDRAM contents to main RAM, optionally downsampling MSAA. + +--- + +## 8. XMA audio and XACT sound banks + +### XMA2WAVEFORMATEX (52 bytes) + +``` +Offset Size Field Notes +0x00 2 wFormatTag 0x0166 (WAVE_FORMAT_XMA2) +0x02 2 nChannels Decoded channel count +0x04 4 nSamplesPerSec Decoded sample rate +0x08 4 nAvgBytesPerSec Encoder internal +0x0C 2 nBlockAlign channels × bitsPerSample / 8 +0x0E 2 wBitsPerSample Always 16 +0x10 2 cbSize Always 34 +0x12 2 NumStreams 1 or 2 channels per stream +0x14 4 ChannelMask SPEAKER_xxx flags +0x18 4 SamplesEncoded Total decoded PCM samples +0x1C 4 BytesPerBlock XMA block size +0x20 4 PlayBegin First valid decoded sample +0x24 4 PlayLength Valid audio length +0x28 4 LoopBegin Loop start (decoded samples) +0x2C 4 LoopLength Loop length +0x30 1 LoopCount 255=infinite, 254=max finite +0x31 1 EncoderVersion ≥3 for XMA2 +0x32 2 BlockCount XMA blocks in file +``` + +### XMA packet structure (2048 bytes, big-endian) + +``` +Bits 0-5: FrameCount (6 bits) Frames beginning in this packet +Bits 6-20: FrameOffsetInBits (15 bits) Bit offset to first complete frame +Bits 21-23: PacketMetaData (3 bits) Always 1 for XMA2 +Bits 24-31: PacketSkipCount (8 bits) Other-stream packets to skip +Bytes 4-2047: Encoded XMA data (2044 bytes) +``` + +Key constants: **XMA_BYTES_PER_PACKET = 2048**, **XMA_SAMPLES_PER_FRAME = 512**, **XMA_SAMPLES_PER_SUBFRAME = 128**. Supported sample rates: 24000, 32000, 44100, 48000 Hz. + +### Hardware XMA decoder at 0x7FEA0000 + +The Southbridge chip provides a hardware XMA decoder accessed via MMIO at virtual address **0x7FEA0000** (64 KB register space). Up to **320 hardware contexts** can be active simultaneously. Each `XMA_CONTEXT_DATA` is 64 bytes (16 DWORDs, big-endian) containing input/output buffer pointers, loop parameters, sample rate, stereo flag, and read/write offsets packed into bitfields. + +### XACT wave bank (XWB) format + +Header magic: **"WBND"** (LE) = **0x444E4257**. The 12-byte header is followed by a segment table (5 entries × 8 bytes: offset + length) pointing to Bank Data, Entry Metadata, Seek Tables, Entry Names, and Wave Data. + +Bank Data flags: `FLAGS_ENTRYNAMES = 0x00010000`, `FLAGS_COMPACT = 0x00020000`, `FLAGS_SEEKTABLES = 0x00080000`. + +### XACT sound bank (XSB) format + +Header magic: **"SDBK"** (LE). The header contains tool/format version, platform byte (0=PC, 2=Xbox), counts of simple/complex cues, wave banks, and sounds, followed by offset tables for cue data, sound entries, variation tables, and wave bank name references. Sound entries are variable-sized: simple sounds (11 bytes: flags + category + volume + track index + wave bank index) vs complex sounds with clip arrays. + +--- + +## 9. XInput gamepad state and USB protocol + +### XINPUT_GAMEPAD (12 bytes at XINPUT_STATE+0x04) + +```c +struct XINPUT_GAMEPAD { + uint16_t wButtons; // +0x00 Bitmask + uint8_t bLeftTrigger; // +0x02 0–255 + uint8_t bRightTrigger; // +0x03 0–255 + int16_t sThumbLX; // +0x04 −32768 to 32767 + int16_t sThumbLY; // +0x06 + int16_t sThumbRX; // +0x08 + int16_t sThumbRY; // +0x0A +}; +``` + +**Button flags:** +``` +DPAD_UP=0x0001 DPAD_DOWN=0x0002 DPAD_LEFT=0x0004 DPAD_RIGHT=0x0008 +START=0x0010 BACK=0x0020 LEFT_THUMB=0x0040 RIGHT_THUMB=0x0080 +LB=0x0100 RB=0x0200 A=0x1000 B=0x2000 +X=0x4000 Y=0x8000 +``` + +Dead zones: **LEFT_THUMB=7849**, **RIGHT_THUMB=8689**, **TRIGGER_THRESHOLD=30**. + +The Xbox 360 controller (VID 0x045E, PID 0x028E) uses **vendor-specific USB class 0xFF** — not standard HID. Interface 0 returns 20-byte input reports at **250 Hz** (4ms interval). Rumble output is an 8-byte report with large motor speed at byte 3 and small motor speed at byte 4. + +--- + +## 10. XNet networking with Winsock-like semantics + +Xbox 360 networking uses `xam.xex` exports wrapping a Winsock-like API with mandatory caller-ID parameters. All `NetDll_*` functions take a first argument specifying caller type (e.g., `XNCALLER_SYSAPP`). + +Key function ordinals from `xam_table.inc`: `NetDll_XNetStartup` (0x33), `NetDll_XNetCleanup` (0x34), `NetDll_XNetRandom` (0x35), `NetDll_XNetCreateKey` (0x36), `NetDll_XNetXnAddrToInAddr` (0x39). + +**XNADDR** is a 36-byte structure combining IP, MAC, and Xbox Live identity. XNet provides built-in IPsec-like encryption for peer-to-peer sessions via `XNetCreateKey`/`XNetRegisterKey`. Xbox Live uses TLS 1.2–based security; detailed packet structures remain proprietary. + +--- + +## 11. Decompilation and disassembly workflow + +### Ghidra with XEXLoaderWV + +**XEXLoaderWV** (by Warranty Voider, `github.com/zeroKilo/XEXLoaderWV`) loads XEX files directly into Ghidra with decryption/decompression, PDB support via MSDIA, and correct base address mapping. Set processor to `PowerPC:BE:32:default`. The loader handles both retail and devkit encryption. **Limitation:** Ghidra's PPC module does not fully support VMX-128's 7-bit register encoding — extended vector instructions may disassemble incorrectly. + +### IDA Pro with idaxex + +**idaxex** (by emoose, `github.com/emoose/idaxex`) supports XEX1/XEX2, all encryption variants, auto-names kernel/XAM imports, and passes CodeView info for PDB loading. Pair with the **PPCAltivec plugin** (`github.com/hayleyxyz/PPC-Altivec-IDA`) for VMX instruction support. The `xkelib.til` type library provides Xbox 360 SDK type definitions. + +### Xenia as dynamic analysis platform + +Xenia provides GPU trace capture (**F4** during gameplay), continuous stream logging (`--trace_gpu_stream`), and a trace viewer (`xe-gpu-trace-viewer`) for inspecting draw calls, register state, and shader microcode. Shader dumps (`--dump_shaders=path/`) output both translated host shaders and original Xenos microcode. Key debug flags: +``` +--debug --log_level=3 --break_on_start=true --trace_gpu_stream=true +``` + +### PowerPC disassembly patterns + +**Function prologue (Microsoft Xbox 360 compiler):** +```asm +mflr r0 ; Save return address +stw r30, -8(r1) ; Save nonvolatile registers into red zone +stw r31, -4(r1) +stw r0, -0xC(r1) ; Save LR on stack +stwu r1, -0x50(r1) ; Allocate frame atomically +``` + +**Function epilogue:** +```asm +lwz r0, 0x44(r1) ; Restore saved LR +lwz r30, 0x48(r1) ; Restore nonvolatiles +lwz r31, 0x4C(r1) +mtlr r0 +addi r1, r1, 0x50 ; Deallocate frame +blr ; Return +``` + +**Import thunk stub (3+ instructions):** +```asm +lis r11, 0x8200 ; Upper 16 bits of IAT entry +ori r11, r11, 0x1234; Lower 16 bits +lwz r11, 0(r11) ; Load resolved function address +mtctr r11 +bctr ; Indirect branch to import +``` + +**Register conventions:** r0 = scratch/LR only, r1 = stack pointer, r2 = TOC, r3 = return value / arg1, r4–r10 = args 2–8, r14–r31 = nonvolatile (callee-saved). Red zone below SP is 232 bytes. + +### Recompilation tools + +**XenonRecomp** (`github.com/hedge-dev/XenonRecomp`) performs static recompilation of Xbox 360 PPC code to native x86-64 C++. **XenosRecomp** (`github.com/hedge-dev/XenosRecomp`) translates Xenos GPU shaders to HLSL for D3D12/Vulkan, using function boundary analysis from `.pdata` exception directory entries. + +--- + +## 12. Encryption and security architecture + +### eFUSE-based per-console keys + +Each Xbox 360 CPU contains **768 one-time-programmable eFuse bits** encoding a unique **16-byte CPU Key** (fusesets 3+5 concatenated with 4+6), update sequence counters (lockdown value preventing downgrade), and devkit/retail flags. The CPU Key encrypts the **KeyVault** — the master store for all per-console cryptographic material. + +### Memory encryption + +The HV implements **per-cache-line (128-byte) AES encryption** with four access pathways: + +| Pathway | Protection | Use | +|---------|-----------|-----| +| 0 | None | Raw/unprotected memory | +| 1 | AES + CRC integrity | HV code and data | +| 2 | CPU SoC internal | Boot ROM, SRAM, eFuses (HV-only) | +| 3 | AES only | Game/kernel executable pages | + +Encryption keys change every boot (hardware RNG), incorporating per-page 10-bit whitening values. This prevents cold-boot replay attacks. + +### XEX key derivation chain + +The HV derives the XEX2 decryption key via **HMAC-SHA-1** using an 8-byte `gCpuSecurityKey` (read from MMIO `0x80000200_000250B8` during init), the 1BL public key (0x110 bytes), 1BL encryption key (0x10 bytes), 1BL salt (10 bytes), and a static 0x590-byte key buffer containing the PIRS verification public key, roamable obfuscation key root, and XeMACS authentication public key. + +### Content license verification + +STFS packages bind licenses to console IDs or Xbox Live accounts (XUIDs). CON packages are signed with the console's private key from the KeyVault; LIVE/PIRS packages use Microsoft's public key. The kernel verifies the RSA signature over the header SHA-1 hash before granting access. The SHA-1 Merkle tree (L0 → L1 → L2 hash tables) ensures data block integrity from individual 4096-byte blocks up to the RSA-signed root hash. + +--- + +## 13. Complete virtual address space and MMIO reference + +### Virtual address map + +| Range | Purpose | +|-------|---------| +| `0x00000000`–`0x0000FFFF` | Null guard / reserved | +| `0x00010000`–`0x3FFFFFFF` | User/title virtual memory (heap, stacks) | +| `0x40000000`–`0x7FE9FFFF` | Extended title space (physical memory mapping) | +| `0x7FEA0000`–`0x7FEAFFFF` | **XMA decoder MMIO** | +| `0x7FFF0000`–`0x7FFFFFFF` | Guard pages | +| `0x80000000`–`0x8FFFFFFF` | Kernel virtual address space | +| `0x82000000` | **Typical game XEX load base** | +| `0x90000000`–`0x9FFFFFFF` | Physical memory identity map (uncached) | +| `0xA0000000`–`0xBFFFFFFF` | Physical memory alternate mappings | +| `0xC0000000`–`0xDFFFFFFF` | Additional physical mapping regions | +| `0xE0000000`–`0xFFFFFFFF` | MMIO / device register space | + +### MMIO register blocks + +| Physical Address | Size | Device | +|-----------------|------|--------| +| `0x7FEA0000` | 64 KB | XMA audio decoder (context array, control registers) | +| `0xC0000000` | ~1 MB | PCI / Southbridge configuration | +| `0xC8000000` | Region | NAND flash controller (SFCX) | +| `0xEA001000` | ~4 KB | Southbridge I/O (SATA, USB) | +| `0xEA001080` | 16 B | SMC write FIFO (System Management Controller) | +| `0xEA001090` | 16 B | SMC read FIFO | +| `0xEC800000` | ~256 KB+ | **Xenos GPU** (command processor, shader regs, display) | + +## Conclusion: a roadmap for re-implementation + +Three insights emerge from this comprehensive analysis that directly inform porting strategy. First, the Xenon's **in-order, VMX-128-heavy execution model** means game code is heavily hand-optimized for specific pipeline timings and the extended 128-register vector file — naïve PPC-to-x64 translation loses these optimizations, making static recompilation tools like XenonRecomp essential starting points. Second, the GPU's **PM4 command stream is fully documented** through Xenia's source, and the shader ISA's kinship with AMD R600 means public ISA manuals cover ~90% of the microcode format, with XenosRecomp handling the translation to modern HLSL. Third, every container format (XEX, STFS, XDVDFS, XWB/XSB) is byte-level documented by the community, enabling complete asset extraction pipelines. The practical path forward combines Xenia's runtime infrastructure for behavioral validation, static recompilation for CPU code, shader translation for GPU code, and format-level extraction for game assets — all grounded in the binary specifications documented above. \ No newline at end of file diff --git a/tools/generate_export_docs.py b/tools/generate_export_docs.py new file mode 100755 index 00000000..61fb09eb --- /dev/null +++ b/tools/generate_export_docs.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +"""Generate documentation of all Xbox 360 system exports in Xenia Canary. + +Parses the export table .inc files and implementation .cc files to produce +a comprehensive listing of all exports with their implementation status. + +Reads a Xenia Canary working tree and writes +`docs/reference/xbox360-exports.{md,json}`. The committed copies were generated +from an older Canary, so a fresh run changes some statuses. + +Usage: + python3 tools/generate_export_docs.py [--canary ../xenia-canary] [--out docs/reference] +""" + +import argparse +import json +import pathlib +import re +from collections import OrderedDict + +SYLPHEED_ROOT = pathlib.Path(__file__).resolve().parent.parent +CANARY_DEFAULT = SYLPHEED_ROOT.parent / "xenia-canary" + +# --- Configuration --- + +# Set by configure() from --canary, before anything reads them. +REPO_ROOT = CANARY_DEFAULT +TABLE_FILES = {} +IMPL_DIRS = {} + + +def configure(canary_root): + """Point the table and implementation paths at a Canary checkout.""" + global REPO_ROOT, TABLE_FILES, IMPL_DIRS + REPO_ROOT = canary_root + TABLE_FILES = { + "xboxkrnl": REPO_ROOT / "src/xenia/kernel/xboxkrnl/xboxkrnl_table.inc", + "xam": REPO_ROOT / "src/xenia/kernel/xam/xam_table.inc", + "xbdm": REPO_ROOT / "src/xenia/kernel/xbdm/xbdm_table.inc", + } + IMPL_DIRS = { + "xboxkrnl": REPO_ROOT / "src/xenia/kernel/xboxkrnl", + "xam": REPO_ROOT / "src/xenia/kernel/xam", + "xbdm": REPO_ROOT / "src/xenia/kernel/xbdm", + } + missing = [str(p) for p in TABLE_FILES.values() if not p.is_file()] + if missing: + raise SystemExit( + f"not a Xenia Canary checkout: {canary_root} " + f"(missing {', '.join(missing)}); pass --canary" + ) + +MODULE_NAMES = { + "xboxkrnl": "xboxkrnl.exe", + "xam": "xam.xex", + "xbdm": "xbdm.xex", +} + +MODULE_MAP = {"XBOXKRNL": "xboxkrnl", "XAM": "xam", "XBDM": "xbdm"} + +# Regex patterns +XE_EXPORT_RE = re.compile( + r"XE_EXPORT\(\s*(\w+)\s*,\s*(0x[0-9A-Fa-f]+)\s*,\s*(\w+)\s*,\s*k(\w+)\s*\)" +) + +DECLARE_RE = re.compile( + r"DECLARE_(XBOXKRNL|XAM|XBDM)_EXPORT\d*\(\s*(\w+)\s*," + r"\s*k(\w+)\s*,\s*(.+?)\)\s*;", + re.DOTALL, +) + +TAG_RE = re.compile(r"k(\w+)") + +SHIM_RE = re.compile(r'SHIM_SET_MAPPING\(\s*"[^"]+"\s*,\s*(\w+)\s*,') + +VAR_MAPPING_RE = re.compile(r"ordinals::(\w+)") +EXPORT_KVAR_RE = re.compile(r"EXPORT_KVAR\((\w+)\)") + + +def parse_table_files(): + """Phase A: Parse all XE_EXPORT entries from *_table.inc files.""" + exports = OrderedDict() + + for module, path in TABLE_FILES.items(): + content = path.read_text() + for match in XE_EXPORT_RE.finditer(content): + mod, ordinal_hex, name, typ = match.groups() + exports[(mod, name)] = { + "module": mod, + "ordinal": int(ordinal_hex, 16), + "ordinal_hex": ordinal_hex.upper().replace("0X", "0x"), + "name": name, + "type": "variable" if typ == "Variable" else "function", + "status": "not_implemented", + "tags": [], + "category": "", + "source_file": "", + } + + return exports + + +def scan_declare_macros(exports): + """Phase B: Scan DECLARE_*_EXPORT macros in .cc files.""" + for mod_key, dir_path in IMPL_DIRS.items(): + for cc_file in sorted(dir_path.glob("*.cc")): + content = cc_file.read_text() + for match in DECLARE_RE.finditer(content): + mod_prefix, name, category, tags_str = match.groups() + module = MODULE_MAP[mod_prefix] + tags = TAG_RE.findall(tags_str) + key = (module, name) + + if key in exports: + entry = exports[key] + entry["category"] = category + entry["tags"] = tags + entry["source_file"] = str( + cc_file.relative_to(REPO_ROOT) + ) + + if "Implemented" in tags: + entry["status"] = "implemented" + elif "Stub" in tags: + entry["status"] = "stub" + elif "Sketchy" in tags: + entry["status"] = "sketchy" + else: + entry["status"] = "stub" + + +def scan_legacy_patterns(exports): + """Phase C: Handle SHIM_SET_MAPPING and SetVariableMapping.""" + # SHIM_SET_MAPPING in xboxkrnl_strings.cc + strings_file = IMPL_DIRS["xboxkrnl"] / "xboxkrnl_strings.cc" + if strings_file.exists(): + content = strings_file.read_text() + for match in SHIM_RE.finditer(content): + name = match.group(1) + key = ("xboxkrnl", name) + if key in exports: + exports[key]["status"] = "implemented" + exports[key]["source_file"] = str( + strings_file.relative_to(REPO_ROOT) + ) + if not exports[key]["tags"]: + exports[key]["tags"] = ["Implemented"] + + # SetVariableMapping / EXPORT_KVAR in xboxkrnl_module.cc + module_file = IMPL_DIRS["xboxkrnl"] / "xboxkrnl_module.cc" + if module_file.exists(): + content = module_file.read_text() + for match in VAR_MAPPING_RE.finditer(content): + name = match.group(1) + key = ("xboxkrnl", name) + if key in exports: + exports[key]["status"] = "implemented" + exports[key]["source_file"] = str( + module_file.relative_to(REPO_ROOT) + ) + if not exports[key]["tags"]: + exports[key]["tags"] = ["Implemented"] + + for match in EXPORT_KVAR_RE.finditer(content): + name = match.group(1) + key = ("xboxkrnl", name) + if key in exports: + exports[key]["status"] = "implemented" + exports[key]["source_file"] = str( + module_file.relative_to(REPO_ROOT) + ) + if not exports[key]["tags"]: + exports[key]["tags"] = ["Implemented"] + + +def compute_statistics(exports): + """Compute per-module statistics.""" + stats = {} + for module in TABLE_FILES: + module_exports = [e for e in exports.values() if e["module"] == module] + stats[module] = { + "total": len(module_exports), + "implemented": sum( + 1 for e in module_exports if e["status"] == "implemented" + ), + "stub": sum(1 for e in module_exports if e["status"] == "stub"), + "sketchy": sum( + 1 for e in module_exports if e["status"] == "sketchy" + ), + "not_implemented": sum( + 1 + for e in module_exports + if e["status"] == "not_implemented" + ), + "functions": sum( + 1 for e in module_exports if e["type"] == "function" + ), + "variables": sum( + 1 for e in module_exports if e["type"] == "variable" + ), + } + return stats + + +def generate_markdown(exports, stats): + """Generate the Markdown documentation.""" + lines = [] + lines.append("# Xbox 360 System Exports - Xenia Canary") + lines.append("") + lines.append( + "Auto-generated by `tools/generate_export_docs.py`. " + "Do not edit manually." + ) + lines.append("") + + # Overall summary + lines.append("## Summary") + lines.append("") + total_all = sum(s["total"] for s in stats.values()) + impl_all = sum(s["implemented"] for s in stats.values()) + stub_all = sum(s["stub"] for s in stats.values()) + sketchy_all = sum(s["sketchy"] for s in stats.values()) + not_impl_all = sum(s["not_implemented"] for s in stats.values()) + + lines.append( + f"**Total exports: {total_all}** | " + f"Implemented: {impl_all} | " + f"Stub: {stub_all} | " + f"Sketchy: {sketchy_all} | " + f"Not implemented: {not_impl_all}" + ) + lines.append("") + + lines.append( + "| Module | File | Total | Implemented | Stub | Sketchy | Not Implemented | Functions | Variables |" + ) + lines.append( + "|--------|------|------:|------------:|-----:|--------:|----------------:|----------:|----------:|" + ) + for module in TABLE_FILES: + s = stats[module] + lines.append( + f"| {module} | {MODULE_NAMES[module]} | {s['total']} | " + f"{s['implemented']} | {s['stub']} | {s['sketchy']} | " + f"{s['not_implemented']} | {s['functions']} | {s['variables']} |" + ) + lines.append("") + + # Per-module tables + for module in TABLE_FILES: + s = stats[module] + lines.append(f"## {module} ({MODULE_NAMES[module]})") + lines.append("") + lines.append( + f"Total: {s['total']} | " + f"Implemented: {s['implemented']} | " + f"Stub: {s['stub']} | " + f"Sketchy: {s['sketchy']} | " + f"Not implemented: {s['not_implemented']}" + ) + lines.append("") + + lines.append( + "| Ordinal | Name | Type | Status | Category | Source |" + ) + lines.append( + "|--------:|------|------|--------|----------|--------|" + ) + + module_exports = sorted( + [e for e in exports.values() if e["module"] == module], + key=lambda e: e["ordinal"], + ) + + for e in module_exports: + ordinal = f"0x{e['ordinal']:03X}" + status_icon = { + "implemented": "implemented", + "stub": "stub", + "sketchy": "sketchy", + "not_implemented": "-", + }.get(e["status"], e["status"]) + + source = e["source_file"] + if source: + # Show just the filename for brevity + source = pathlib.Path(source).name + + lines.append( + f"| {ordinal} | {e['name']} | {e['type']} | " + f"{status_icon} | {e['category']} | {source} |" + ) + + lines.append("") + + return "\n".join(lines) + + +def generate_json(exports, stats): + """Generate the JSON documentation.""" + data = { + "description": "Xbox 360 system exports implemented in Xenia Canary", + "generator": "tools/generate_export_docs.py", + "statistics": stats, + "modules": {}, + } + + for module in TABLE_FILES: + module_exports = sorted( + [e for e in exports.values() if e["module"] == module], + key=lambda e: e["ordinal"], + ) + data["modules"][module] = { + "file": MODULE_NAMES[module], + "exports": module_exports, + } + + return json.dumps(data, indent=2) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--canary", type=pathlib.Path, default=CANARY_DEFAULT, + help="Xenia Canary working tree (default: %(default)s)") + parser.add_argument("--out", type=pathlib.Path, + default=SYLPHEED_ROOT / "docs" / "reference", + help="output directory (default: %(default)s)") + args = parser.parse_args() + configure(args.canary.resolve()) + + print("Parsing export tables...") + exports = parse_table_files() + print(f" Found {len(exports)} exports across {len(TABLE_FILES)} modules") + + print("Scanning DECLARE_*_EXPORT macros...") + scan_declare_macros(exports) + + print("Scanning legacy registration patterns...") + scan_legacy_patterns(exports) + + stats = compute_statistics(exports) + + for module, s in stats.items(): + print( + f" {module}: {s['total']} total, " + f"{s['implemented']} implemented, " + f"{s['stub']} stub, " + f"{s['sketchy']} sketchy, " + f"{s['not_implemented']} not implemented" + ) + + # Generate outputs + docs_dir = args.out + docs_dir.mkdir(parents=True, exist_ok=True) + + md_path = docs_dir / "xbox360-exports.md" + md_content = generate_markdown(exports, stats) + md_path.write_text(md_content) + print(f"Written: {md_path}") + + json_path = docs_dir / "xbox360-exports.json" + json_content = generate_json(exports, stats) + json_path.write_text(json_content) + print(f"Written: {json_path}") + + +if __name__ == "__main__": + main()