Files
xex2tractor/doc/xex2_format.md
MechaCat02 abbd264e4c Initial project setup
Scaffold xex2tractor Rust project with cargo, add MIT license, README,
and XEX2 file format documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 18:05:25 +01:00

38 KiB

XEX2 File Format Documentation

This document describes the XEX2 (Xbox 360 Executable) file format as implemented in the Xenia emulator. All multi-byte values in the XEX2 file are big-endian unless otherwise noted. The contained PE image uses big-endian values as well (PowerPC BE target).

Terminology:

  • XEX offset: byte offset from the start of the .xex file on disk
  • PE offset: byte offset from the start of the decompressed/decrypted PE image (which begins at xex2_header.header_size in the raw file, but after decryption/decompression is loaded at the base address)
  • Memory address: Xbox 360 virtual address (typically starting at the load_address from security info, e.g. 0x82000000)

1. Top-Level XEX2 File Layout

+==================================+  XEX offset 0x00
|        xex2_header               |
|  (magic, flags, header_size,     |
|   security_offset, opt headers)  |
+----------------------------------+  XEX offset 0x18
|  Optional Headers Array          |
|  (header_count entries of        |
|   xex2_opt_header, 8 bytes each) |
+----------------------------------+  XEX offset = security_offset
|  xex2_security_info              |
|  (RSA sig, AES key, pages, ...)  |
+----------------------------------+  XEX offset varies
|  Optional Header Data            |
|  (pointed to by opt headers)     |
+==================================+  XEX offset = header_size
|  Encrypted/Compressed PE Image   |
|  (the actual executable payload) |
+==================================+  XEX offset = end of file

2. Main XEX2 Header (xex2_header)

Located at XEX offset 0x00.

Offset Size Field Description
0x00 4 magic Magic bytes: XEX2 (0x58455832)
0x04 4 module_flags Bitfield of xex2_module_flags (see below)
0x08 4 header_size Total size of all headers in bytes. The PE image data starts at this XEX offset.
0x0C 4 reserved Reserved (typically 0)
0x10 4 security_offset XEX offset to the xex2_security_info structure (from start of file)
0x14 4 header_count Number of optional header entries following
0x18 8 * N headers[N] Array of xex2_opt_header entries

Module Flags (xex2_module_flags, bitmask)

Value Name Description
0x00000001 XEX_MODULE_TITLE Main game/app executable
0x00000002 XEX_MODULE_EXPORTS_TO_TITLE Module exports functions to titles
0x00000004 XEX_MODULE_SYSTEM_DEBUGGER System debugger module
0x00000008 XEX_MODULE_DLL_MODULE DLL module
0x00000010 XEX_MODULE_MODULE_PATCH Module patch
0x00000020 XEX_MODULE_PATCH_FULL Full patch (replaces entire module)
0x00000040 XEX_MODULE_PATCH_DELTA Delta patch (applies diffs)
0x00000080 XEX_MODULE_USER_MODE User-mode module

3. Optional Header Entry (xex2_opt_header)

Each entry is 8 bytes, located starting at XEX offset 0x18.

Offset Size Field Description
0x00 4 key Header key identifier (xex2_header_keys enum)
0x04 4 value / offset Interpretation depends on low byte of key

How the value/offset field is interpreted

The low byte (key & 0xFF) determines the meaning:

Low byte Meaning
0x00 The 4-byte value field is the data itself (inline uint32_t).
0x01 The value field is the data itself (stored in-place, pointer to the 4-byte field within the header).
Any other offset is an XEX offset (from start of file) pointing to the actual header data structure.

Optional Header Keys (xex2_header_keys)

Key Value Name Data Size/Type Description
0x000002FF XEX_HEADER_RESOURCE_INFO Variable Embedded resource descriptors
0x000003FF XEX_HEADER_FILE_FORMAT_INFO Variable Encryption + compression info
0x000005FF XEX_HEADER_DELTA_PATCH_DESCRIPTOR Variable Delta patch descriptor
0x00000405 XEX_HEADER_BASE_REFERENCE Variable Base reference for patches
0x00004304 XEX_HEADER_DISC_PROFILE_ID 4 bytes Disc profile ID
0x000080FF XEX_HEADER_BOUNDING_PATH Variable string Bounding path
0x00008105 XEX_HEADER_DEVICE_ID 20 bytes Device ID
0x00010001 XEX_HEADER_ORIGINAL_BASE_ADDRESS Inline u32 Original PE base address
0x00010100 XEX_HEADER_ENTRY_POINT Inline u32 Program entry point (memory address)
0x00010201 XEX_HEADER_IMAGE_BASE_ADDRESS Inline u32 Load base address override
0x000103FF XEX_HEADER_IMPORT_LIBRARIES Variable Import library table
0x00018002 XEX_HEADER_CHECKSUM_TIMESTAMP 8 bytes Checksum + timestamp
0x00018102 XEX_HEADER_ENABLED_FOR_CALLCAP 8 bytes Callcap thunk addresses
0x00018200 XEX_HEADER_ENABLED_FOR_FASTCAP Inline u32 Fastcap enabled
0x000183FF XEX_HEADER_ORIGINAL_PE_NAME Variable string Original PE file name
0x000200FF XEX_HEADER_STATIC_LIBRARIES Variable Linked static library info
0x00020104 XEX_HEADER_TLS_INFO 16 bytes Thread-Local Storage info
0x00020200 XEX_HEADER_DEFAULT_STACK_SIZE Inline u32 Default stack size
0x00020301 XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE Inline u32 FS cache size
0x00020401 XEX_HEADER_DEFAULT_HEAP_SIZE Inline u32 Default heap size
0x00028002 XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS 8 bytes Page heap config
0x00030000 XEX_HEADER_SYSTEM_FLAGS Inline u32 System privilege flags
0x00030100 XEX_HEADER_SYSTEM_FLAGS_32 Inline u32 Extended system flags (Kinect, etc.)
0x00030200 XEX_HEADER_SYSTEM_FLAGS_64 Inline u32 64-bit privilege flags
0x00040006 XEX_HEADER_EXECUTION_INFO 24 bytes Title ID, media ID, disc info
0x00040201 XEX_HEADER_TITLE_WORKSPACE_SIZE Inline u32 Title workspace size
0x00040310 XEX_HEADER_GAME_RATINGS 64 bytes Game content ratings
0x00040404 XEX_HEADER_LAN_KEY 16 bytes LAN encryption key
0x000405FF XEX_HEADER_XBOX360_LOGO Variable Xbox 360 logo bitmap
0x000406FF XEX_HEADER_MULTIDISC_MEDIA_IDS Variable Multi-disc media IDs
0x000407FF XEX_HEADER_ALTERNATE_TITLE_IDS Variable Alternate title IDs
0x00040801 XEX_HEADER_ADDITIONAL_TITLE_MEMORY Inline u32 Extra title memory
0x00E10402 XEX_HEADER_EXPORTS_BY_NAME 8 bytes PE export directory info

4. Security Info (xex2_security_info)

Located at XEX offset = xex2_header.security_offset (from start of file).

Offset Size Field Description
0x000 4 header_size Size of this security info structure
0x004 4 image_size Size of the decompressed PE image
0x008 256 (0x100) rsa_signature RSA-2048 signature over the header
0x108 4 unk_108 Unknown (length field?)
0x10C 4 image_flags xex2_image_flags bitmask
0x110 4 load_address Virtual memory address where the PE is loaded (e.g. 0x82000000)
0x114 20 (0x14) section_digest SHA-1 digest of section data
0x128 4 import_table_count Number of import table entries
0x12C 20 (0x14) import_table_digest SHA-1 digest of import table
0x140 16 (0x10) xgd2_media_id XGD2 media identifier
0x150 16 (0x10) aes_key Encrypted AES-128 session key (see Encryption section)
0x160 4 export_table Memory address of the XEX export table (0 if none)
0x164 20 (0x14) header_digest SHA-1 digest of header
0x178 4 region Allowed regions (xex2_region_flags)
0x17C 4 allowed_media_types Allowed media types (xex2_media_flags)
0x180 4 page_descriptor_count Number of page descriptors following
0x184 24 * N page_descriptors[N] Array of xex2_page_descriptor entries

Image Flags (xex2_image_flags, bitmask)

Value Name
0x00000002 Manufacturing utility
0x00000004 Manufacturing support tools
0x00000008 XGD2 media only
0x00000100 Cardea key
0x00000200 Xeika key
0x00000400 Usermode title
0x00000800 Usermode system
0x10000000 4KB page size (otherwise 64KB)
0x20000000 Region free
0x40000000 Revocation check optional
0x80000000 Revocation check required

Region Flags (xex2_region_flags, bitmask)

Value Region
0x000000FF NTSC/U (North America)
0x0000FF00 NTSC/J (Japan + Asia)
0x00000100 NTSC/J - Japan
0x00000200 NTSC/J - China
0x00FF0000 PAL (Europe)
0x00010000 PAL - Australia/New Zealand
0xFF000000 Other regions
0xFFFFFFFF All regions (region-free)

Media Flags (xex2_media_flags, bitmask)

Value Media Type
0x00000001 Hard disk
0x00000002 DVD X2
0x00000004 DVD/CD
0x00000008 DVD-5
0x00000010 DVD-9
0x00000020 System flash
0x00000080 Memory unit
0x00000100 USB mass storage
0x00000200 Network
0x00000400 Direct from memory
0x00000800 RAM drive
0x00001000 SVOD
0x01000000 Insecure package
0x02000000 Savegame package
0x04000000 Locally signed package
0x08000000 LIVE signed package
0x10000000 Xbox package

5. Page Descriptors (xex2_page_descriptor)

Each page descriptor is 24 bytes and immediately follows page_descriptor_count in the security info (starting at XEX offset = security_offset + 0x184).

+----------------------------------+
| Bits 31-28 | Bits 27-0          |  0x00  (4 bytes, combined bitfield)
| info (4b)  | page_count (28b)   |
+----------------------------------+
| data_digest (20 bytes, SHA-1)    |  0x04
+----------------------------------+
Offset Size Field Description
0x00 4 value Combined bitfield (big-endian, must be byte-swapped before reading bits)
.info (bits 31-28) Section type: 1=Code, 2=Data, 3=Read-only data
.page_count (bits 27-0) Number of pages in this section
0x04 20 data_digest SHA-1 hash of the page data

Page size is determined by XEX_IMAGE_PAGE_SIZE_4KB in image flags:

  • If set: 4 KB (0x1000) pages
  • If not set: 64 KB (0x10000) pages

Memory mapping: Pages are mapped sequentially starting at load_address. For page descriptor i, the memory address is:

address = load_address + (sum of all previous page_counts) * page_size
size = desc.page_count * page_size

Section types determine memory protection:

Type Value Protection
XEX_SECTION_CODE 1 Read-only (or Read+Write if writable_code_segments)
XEX_SECTION_DATA 2 Read + Write
XEX_SECTION_READONLY_DATA 3 Read-only

6. Encryption

Overview

XEX2 uses a two-level AES-128-CBC encryption scheme:

  1. The session key (per-XEX) is stored encrypted in xex2_security_info.aes_key (at security info offset 0x150).
  2. This session key is itself encrypted with one of the well-known master keys.
  3. The session key is then used to decrypt the PE image payload.

Master AES-128 Keys

Key Value (hex) Usage
XEX2 Retail 20 B1 85 A5 9D 28 FD C3 40 58 3F BB 08 96 BF 91 Production/retail XEX2 files
XEX2 DevKit 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Development kit XEX2 files (null key)
XEX1 Retail A2 6C 10 F7 1F D9 35 E9 8B 99 92 2C E9 32 15 72 Legacy XEX1 format

Key Derivation Process

1. Read encrypted_session_key from xex2_security_info.aes_key[0x10]
2. Decrypt encrypted_session_key using master_key with AES-128-CBC (IV = 0)
   → This yields the session_key[16]
3. Use session_key to decrypt the PE image payload with AES-128-CBC (IV = 0)

AES-128-CBC Decryption Algorithm

The decryption used is standard AES-128 in CBC mode with a zero IV (16 bytes of 0x00):

Input: session_key[16], ciphertext, length
State: IV[16] = {0, 0, ..., 0}
       rk[] = rijndaelKeySetupDec(session_key, 128)  // 128-bit key

For each 16-byte block:
    plaintext_block = rijndaelDecrypt(rk, Nr, ciphertext_block)
    plaintext_block ^= IV          // XOR with previous ciphertext (or IV for first block)
    IV = ciphertext_block          // Update IV to current ciphertext

Implementation: Uses the Rijndael reference implementation (rijndael-alg-fst.c), with Nr rounds returned by rijndaelKeySetupDec() (10 rounds for AES-128).

Key Trial Order

The loader tries keys in this order, falling back on failure:

  1. XEX2 Retail key
  2. XEX2 DevKit key (all zeros)
  3. XEX1 Retail key

Success is determined by checking if the decrypted image begins with a valid PE signature (MZ / 0x5A4D).

Encryption Type (xex2_encryption_type)

Stored in xex2_opt_file_format_info.encryption_type:

Value Name Description
0 XEX_ENCRYPTION_NONE PE image is not encrypted
1 XEX_ENCRYPTION_NORMAL PE image is AES-128-CBC encrypted

7. Compression

Compression type is stored in xex2_opt_file_format_info.compression_type.

File Format Info (xex2_opt_file_format_info)

Pointed to by optional header key 0x000003FF (XEX_HEADER_FILE_FORMAT_INFO).

Offset Size Field Description
0x00 4 info_size Total size of this structure
0x04 2 encryption_type 0=None, 1=Normal (AES-128-CBC)
0x06 2 compression_type 0=None, 1=Basic, 2=Normal, 3=Delta
0x08 ... compression_info Union: basic or normal compression info

Compression Types

Value Name Description
0 XEX_COMPRESSION_NONE No compression; raw PE image data
1 XEX_COMPRESSION_BASIC Block-based zero-fill compression
2 XEX_COMPRESSION_NORMAL LZX (Lempel-Ziv extended) compression with SHA-1 block chaining
3 XEX_COMPRESSION_DELTA Delta patch compression (for update patches)

7a. No Compression (XEX_COMPRESSION_NONE)

The PE image starts at XEX offset = header_size and extends to end of file. The raw data length is xex_file_size - header_size. If encrypted, the entire payload is decrypted in-place with AES-128-CBC using the session key.


7b. Basic Compression (XEX_COMPRESSION_BASIC)

The compression info contains an array of block descriptors that describe alternating data and zero-filled regions.

Basic Compression Block (xex2_file_basic_compression_block)

Located at xex2_opt_file_format_info offset 0x08. The number of blocks is (info_size - 8) / 8.

Offset Size Field Description
0x00 4 data_size Bytes of real data to copy from the XEX payload
0x04 4 zero_size Bytes of zeros to append after the data

Decompression process (after AES-128-CBC decryption if encryption_type == NORMAL):

source_ptr = XEX file + header_size    // start of PE payload in XEX file
dest_ptr   = base_address in memory    // Xbox 360 virtual memory

For each block[i]:
    Copy block[i].data_size bytes from source_ptr to dest_ptr
    Advance source_ptr by data_size
    Zero-fill block[i].zero_size bytes at dest_ptr + data_size
    Advance dest_ptr by (data_size + zero_size)

The total uncompressed size = sum of all (data_size + zero_size) across all blocks.

Note on encryption with basic compression: When encryption is NORMAL, the AES-128-CBC decryption is performed inline per block — the CBC IV state carries across block boundaries (it is NOT reset per block). The same session_key and continuous CBC state are used.


7c. Normal Compression (XEX_COMPRESSION_NORMAL)

This is a two-stage process: de-blocking, then LZX decompression.

Normal Compression Info (xex2_file_normal_compression_info)

Located at xex2_opt_file_format_info offset 0x08:

Offset Size Field Description
0x00 4 window_size LZX decompression window size in bytes (must be power of 2)
0x04 4 first_block.block_size Size of the first compressed block in bytes
0x08 20 first_block.block_hash SHA-1 hash of the first block's data

Compressed Block Info (xex2_compressed_block_info)

Each block in the compressed stream is described by a chained structure:

Offset Size Field Description
0x00 4 block_size Total size of this block in bytes (0 = end of chain)
0x04 20 block_hash SHA-1 hash of this block's data

Block chaining: The block_size and block_hash of the next block are stored at the beginning of the current block's data. This creates a hash chain for integrity verification.

Decompression Process

1. DECRYPT (if encrypted):
   Decrypt the entire PE payload (XEX file + header_size, length = file_size - header_size)
   using AES-128-CBC with session_key and zero IV.

2. DE-BLOCK:
   current_block_info = first_block from compression_info header
   source_ptr = start of decrypted payload
   dest_buffer = temporary buffer

   While current_block_info.block_size != 0:
       a. Verify SHA-1(source_ptr, current_block_info.block_size) == current_block_info.block_hash
       b. Read next_block_info from source_ptr:
            next_block_size  = bytes [0..3]   (4 bytes)
            next_block_hash  = bytes [4..23]  (20 bytes)
       c. Skip past block header (4 + 20 = 24 bytes)
       d. Read data chunks:
            While true:
                chunk_size = read 2 bytes (big-endian uint16)
                If chunk_size == 0: break  (end of block)
                Copy chunk_size bytes to dest_buffer
       e. Advance source_ptr to: previous source_ptr + current_block_info.block_size
       f. current_block_info = next_block_info

3. LZX DECOMPRESS:
   Decompress dest_buffer using LZX algorithm:
     - Input: de-blocked data
     - Output size: image_size (from page descriptors sum)
     - Window size: compression_info.normal.window_size
     - Reset interval: 0 (no reset)
     - Frame size: 0x8000 (32 KB)

   Output is written to memory at base_address.

LZX Algorithm Details

  • Algorithm: LZX (Lempel-Ziv Extended), the same algorithm used in Microsoft CAB files
  • Implementation: mspack library (lzxd.c)
  • Window size: Specified per-XEX in window_size field (typically a power of 2; common values include 0x20000 = 128KB)
  • Window bits: log2(window_size) — computed via bit scan
  • Frame size: Fixed at 0x8000 (32,768 bytes)
  • Reset interval: 0 (no periodic state reset)

7d. Delta Compression / Patching (XEX_COMPRESSION_DELTA)

Used for XEX patches (XEXP files). The patch XEX has XEX_MODULE_PATCH_DELTA set in module_flags.

Delta Patch Descriptor (xex2_opt_delta_patch_descriptor)

Pointed to by optional header key 0x000005FF:

Offset Size Field Description
0x00 4 size Size of the header patch data
0x04 4 target_version_value Target version after patch (xex2_version bitfield)
0x08 4 source_version_value Source version required (xex2_version bitfield)
0x0C 20 digest_source SHA-1 digest of source image
0x20 16 image_key_source Key verification data
0x30 4 size_of_target_headers Size of target XEX headers after patch
0x34 4 delta_headers_source_offset Offset within source XEX headers to copy from
0x38 4 delta_headers_source_size Size of source header data to copy
0x3C 4 delta_headers_target_offset Offset within target XEX headers to copy to
0x40 4 delta_image_source_offset Offset within source PE image to copy from
0x44 4 delta_image_source_size Size of source image data to copy
0x48 4 delta_image_target_offset Offset within target PE image to copy to
0x4C ... info First xex2_delta_patch entry (inline)

Delta Patch Entry (xex2_delta_patch)

Offset Size Field Description
0x00 4 old_addr Offset in the existing memory image to read source data from
0x04 4 new_addr Offset in the existing memory image to write patched data to
0x08 2 uncompressed_len Size of decompressed output
0x0A 2 compressed_len Size of compressed patch data (special values below)
0x0C ... patch_data Compressed patch data (variable length)

Special compressed_len values:

Value Action
0 Zero-fill: memset(dest + new_addr, 0, uncompressed_len)
1 Copy: memcpy(dest + new_addr, dest + old_addr, uncompressed_len)
>= 2 LZX delta decompress: decompress patch_data using old_addr data as window reference

Delta Patch Key Handling

Delta patches use a three-level key scheme:

  1. Base module's session key is decrypted using the master key (as normal)
  2. The patch's encrypted AES key is then decrypted using the base module's session key (not the master key)
  3. Verification: AES_Decrypt(base_session_key, patch_descriptor.image_key_source) must equal the original session key of the base module

8. Import Libraries

Located via optional header key 0x000103FF (XEX_HEADER_IMPORT_LIBRARIES).

Import Libraries Container (xex2_opt_import_libraries)

Offset Size Field Description
0x00 4 size Total size of the import libraries structure
0x04 4 string_table.size Size of the string table in bytes
0x08 4 string_table.count Number of strings in the table
0x0C N string_table.data Null-terminated strings, 4-byte aligned with padding

Library entries follow immediately after the string table (at offset string_table.size + 12).

Import Library (xex2_import_library)

Each library entry:

Offset Size Field Description
0x00 4 size Size of this library entry in bytes
0x04 20 next_import_digest SHA-1 digest of next import entry
0x18 4 id Library ID
0x1C 4 version_value Library version (xex2_version bitfield)
0x20 4 version_min_value Minimum required version
0x24 2 name_index Index (low byte) into the string table
0x26 2 count Number of import records
0x28 4 * N import_table[N] Array of import record memory addresses

Import Record Format (in memory)

Each entry in import_table is a memory address pointing to a location within the loaded PE image. At that memory address, the value is:

Bits 31-24 (byte 0): record_type
    0x00 = Variable import
    0x01 = Thunk (function) import
Bits 15-0  (bytes 2-3): ordinal number

Variable imports (record_type == 0): The memory slot is overwritten with:

  • For kernel exports (implemented): the variable's address
  • For kernel exports (not implemented): 0xD000BEEF | (ordinal & 0xFFF) << 16
  • For user module exports: the export address
  • For unresolved imports: 0xF00DF00D

Thunk imports (record_type == 1): The 16-byte thunk in memory is originally:

+0x00: li r3, 0               // 0x38600000
+0x04: li r4, <ordinal>       // 0x38800000 | ordinal
+0x08: mtspr CTR, r11         // 0x7D6903A6
+0x0C: bctr                   // 0x4E800420

For user module imports, this is rewritten to:

+0x00: lis r11, <addr_hi>     // 0x3D600000 | (addr >> 16)
+0x04: ori r11, r11, <addr_lo>// 0x616B0000 | (addr & 0xFFFF)
+0x08: mtspr CTR, r11         // (unchanged)
+0x0C: bctr                   // (unchanged)

Import records alternate: variable descriptor, then thunk address, then next variable descriptor, etc.


9. Export Table (xex2_export_table)

Located at the memory address specified in xex2_security_info.export_table. This is a virtual address, NOT a file offset.

Offset Size Field Description
0x00 12 magic[3] Magic identifier (3 uint32_t values)
0x0C 8 modulenumber[2] Module number (2 uint32_t values)
0x14 12 version[3] Version info (3 uint32_t values)
0x20 4 imagebaseaddr Image base address (must be shifted left 16 bits to get actual address)
0x24 4 count Number of exports
0x28 4 base Base ordinal number
0x2C 4 * N ordOffset[N] Array of ordinal offsets

Resolving an export address:

function_address = ordOffset[ordinal - base] + (imagebaseaddr << 16)

PE Export Directory (X_IMAGE_EXPORT_DIRECTORY)

An alternative export mechanism via the PE header (optional header key XEX_HEADER_EXPORTS_BY_NAME). The xex2_opt_data_directory at that key contains:

  • offset: RVA from PE base to the X_IMAGE_EXPORT_DIRECTORY
  • size: Size of the export directory

The export directory is standard PE format (little-endian within the Xbox PE):

Offset Size Field
0x00 4 Characteristics
0x04 4 TimeDateStamp
0x08 2 MajorVersion
0x0A 2 MinorVersion
0x0C 4 Name (RVA)
0x10 4 Base ordinal
0x14 4 NumberOfFunctions
0x18 4 NumberOfNames
0x1C 4 AddressOfFunctions (RVA from export directory)
0x20 4 AddressOfNames (RVA from export directory)
0x24 4 AddressOfNameOrdinals (RVA from export directory)

10. Specific Optional Header Structures

Execution Info (xex2_opt_execution_info) — Key 0x00040006

24 bytes (0x18). All offsets relative to start of structure.

Offset Size Field Description
0x00 4 media_id Media identifier
0x04 4 version_value Module version (xex2_version bitfield)
0x08 4 base_version_value Base version
0x0C 4 title_id Title ID (e.g. 0x415607D1)
0x10 1 platform Platform identifier
0x11 1 executable_table Executable table index
0x12 1 disc_number Current disc number
0x13 1 disc_count Total disc count
0x14 4 savegame_id Savegame identifier

Version Bitfield (xex2_version)

Packed into a 32-bit big-endian value:

Bits Field Width
31-28 major 4 bits
27-24 minor 4 bits
23-8 build 16 bits
7-0 qfe 8 bits

TLS Info (xex2_opt_tls_info) — Key 0x00020104

16 bytes (0x10):

Offset Size Field Description
0x00 4 slot_count Number of TLS slots
0x04 4 raw_data_address Memory address of TLS raw data
0x08 4 data_size Total TLS data size
0x0C 4 raw_data_size Size of initialized TLS data

Checksum / Timestamp (xex2_opt_checksum_timedatestamp) — Key 0x00018002

8 bytes:

Offset Size Field Description
0x00 4 checksum Module checksum
0x04 4 timedatestamp Unix timestamp of build

Resource Info (xex2_opt_resource_info) — Key 0x000002FF

Variable size. Resource count = (size - 4) / 16.

Offset Size Field Description
0x00 4 size Total size of resource info
0x04 16 * N resources[N] Array of xex2_resource

Each xex2_resource (16 bytes):

Offset Size Field Description
0x00 8 name Resource name (null-padded)
0x08 4 address Memory address of resource
0x0C 4 size Size of resource in bytes

Static Libraries (xex2_opt_static_libraries) — Key 0x000200FF

Offset Size Field Description
0x00 4 size Total size. Library count = (size - 4) / 16
0x04 16 * N libraries[N] Array of xex2_opt_static_library

Each xex2_opt_static_library (16 bytes / 0x10):

Offset Size Field Description
0x00 8 name Library name (null-padded)
0x08 2 version_major Major version
0x0A 2 version_minor Minor version
0x0C 2 version_build Build number
0x0E 1 approval_type 0=Unapproved, 1=Possible, 2=Approved, 3=Expired
0x0F 1 version_qfe QFE version

LAN Key (xex2_opt_lan_key) — Key 0x00040404

16 bytes: raw AES-128 key used for LAN multiplayer encryption.

Game Ratings (xex2_game_ratings_t) — Key 0x00040310

64 bytes (0x40) containing age ratings for various regional rating boards:

Offset Size Board
0x00 1 ESRB (North America)
0x01 1 PEGI (Europe)
0x02 1 PEGI Finland
0x03 1 PEGI Portugal
0x04 1 BBFC (UK/Ireland)
0x05 1 CERO (Japan)
0x06 1 USK (Germany)
0x07 1 OFLC Australia
0x08 1 OFLC New Zealand
0x09 1 KMRB (South Korea)
0x0A 1 Brazil
0x0B 1 FPB (South Africa)
0x0C 52 Reserved / Unknown

Each rating value is 0xFF for "Unrated".

Callcap Imports (xex2_opt_call_cap_imports) — Key 0x00018102

8 bytes:

Offset Size Field Description
0x00 4 start_func_thunk_addr Memory address of start function thunk
0x04 4 end_func_thunk_addr Memory address of end function thunk

Data Directory (xex2_opt_data_directory) — Key 0x00E10402

8 bytes, used for PE exports-by-name:

Offset Size Field Description
0x00 4 offset RVA from PE image base
0x04 4 size Size of the directory

Bound Path (xex2_opt_bound_path) — Key 0x000080FF

Variable length:

Offset Size Field Description
0x00 4 size Total size including this field
0x04 N path Null-terminated path string

Original PE Name (xex2_opt_original_pe_name) — Key 0x000183FF

Variable length:

Offset Size Field Description
0x00 4 size Total size including this field
0x04 N name Null-terminated original PE filename

11. PE Image (After Decryption/Decompression)

After decryption and decompression, the PE image is loaded into Xbox 360 virtual memory at load_address (from security info, or overridden by XEX_HEADER_IMAGE_BASE_ADDRESS).

PE Headers (in memory at load_address)

The PE image is a standard 32-bit PE executable for PowerPC Big-Endian:

DOS Header (at memory base address)

Offset Size Field Description
0x00 2 e_magic MZ signature (0x5A4D) — note: stored as 0x905A4D with byte swap check
... ... ... Standard DOS header fields
0x3C 4 e_lfanew Offset to NT headers (from start of PE image)

NT Headers (at PE offset e_lfanew)

Offset Size Field Description
0x00 4 Signature PE\0\0 (0x00004550)
0x04 20 FileHeader COFF file header
0x18 224 OptionalHeader PE32 optional header

File Header Validation

Field Expected Value Description
Machine 0x01F2 IMAGE_FILE_MACHINE_POWERPCBE
Characteristics bit 0x0100 set IMAGE_FILE_32BIT_MACHINE
SizeOfOptionalHeader 224 (0xE0) Standard PE32 optional header size

Optional Header Validation

Field Expected Value
Magic 0x10B (IMAGE_NT_OPTIONAL_HDR32_MAGIC)
Subsystem 14 (IMAGE_SUBSYSTEM_XBOX)

Section Headers

Located immediately after the optional header. Each section header is 40 bytes:

Offset Size Field Description
0x00 8 Name Section name (e.g. .text, .rdata, .data)
0x08 4 VirtualSize Size in memory
0x0C 4 VirtualAddress RVA from PE image base
0x10 4 SizeOfRawData Size of raw data
0x14 4 PointerToRawData PE offset to raw data
0x18 4 PointerToRelocations (not used)
0x1C 4 PointerToLinenumbers (not used)
0x20 2 NumberOfRelocations (not used)
0x22 2 NumberOfLinenumbers (not used)
0x24 4 Characteristics Section flags

Section characteristics relevant to Xbox 360:

Value Name
0x00000020 Contains code
0x00000040 Contains initialized data
0x00000080 Contains uninitialized data
0x20000000 Memory execute
0x40000000 Memory read
0x80000000 Memory write

The in-memory address of a section is: load_address + VirtualAddress


12. System Flags (xex2_system_flags)

Inline u32 at optional header key 0x00030000. Bitmask of system privileges:

Value Name Description
0x00000001 NO_FORCED_REBOOT
0x00000002 FOREGROUND_TASKS
0x00000004 NO_ODD_MAPPING
0x00000008 HANDLE_MCE_INPUT
0x00000010 RESTRICTED_HUD_FEATURES
0x00000020 HANDLE_GAMEPAD_DISCONNECT
0x00000040 INSECURE_SOCKETS
0x00000080 XBOX1_INTEROPERABILITY
0x00000100 DASH_CONTEXT
0x00000200 USES_GAME_VOICE_CHANNEL
0x00000400 PAL50_INCOMPATIBLE
0x00000800 INSECURE_UTILITY_DRIVE
0x00001000 XAM_HOOKS
0x00002000 ACCESS_PII
0x00004000 CROSS_PLATFORM_SYSTEM_LINK
0x00008000 MULTIDISC_SWAP
0x00010000 MULTIDISC_INSECURE_MEDIA
0x00020000 AP25_MEDIA Anti-piracy 2.5 media check
0x00040000 NO_CONFIRM_EXIT
0x00080000 ALLOW_BACKGROUND_DOWNLOAD
0x00100000 CREATE_PERSISTABLE_RAMDRIVE
0x00200000 INHERIT_PERSISTENT_RAMDRIVE
0x00400000 ALLOW_HUD_VIBRATION
0x00800000 ACCESS_UTILITY_PARTITIONS
0x01000000 IPTV_INPUT_SUPPORTED
0x02000000 PREFER_BIG_BUTTON_INPUT
0x04000000 ALLOW_EXTENDED_SYSTEM_RESERVATION
0x08000000 MULTIDISC_CROSS_TITLE
0x10000000 INSTALL_INCOMPATIBLE
0x20000000 ALLOW_AVATAR_GET_METADATA_BY_XUID
0x40000000 ALLOW_CONTROLLER_SWAPPING
0x80000000 DASH_EXTENSIBILITY_MODULE

Extended System Flags (32-bit) — Key 0x00030100

Value Name
0x00000001 ALLOW_NETWORK_READ_CANCEL
0x00000002 UNINTERRUPTABLE_READS
0x00000004 REQUIRE_FULL_EXPERIENCE
0x00000008 GAME_VOICE_REQUIRED_UI
0x00000010 TITLE_SET_PRESENCE_STRING
0x00000020 CAMERA_ANGLE_CONTROL
0x00000040 SKELETAL_TRACKING_REQUIRED
0x00000080 SKELETAL_TRACKING_SUPPORTED

13. Complete Loading Sequence

Here is the full loading process as implemented by Xenia:

1. READ HEADER
   a. Read xex2_header from offset 0
   b. Verify magic == "XEX2" (0x58455832)
   c. Copy entire header region (header_size bytes) into memory

2. PARSE SECURITY INFO
   a. Navigate to xex2_header.security_offset
   b. Extract: RSA signature, encrypted AES key, load_address, image_flags,
      export_table address, page_descriptors
   c. Determine base_address: use XEX_HEADER_IMAGE_BASE_ADDRESS if present,
      otherwise security_info.load_address

3. DECRYPT & DECOMPRESS PE IMAGE
   a. Determine encryption/compression from XEX_HEADER_FILE_FORMAT_INFO
   b. Derive session key:
        session_key = AES-128-CBC-Decrypt(master_key, security_info.aes_key)
      Try retail key first, then devkit, then XEX1 key
   c. Based on compression_type:
      - NONE: decrypt payload directly to base_address
      - BASIC: decrypt + zero-fill blocks to base_address
      - NORMAL: decrypt → de-block → LZX decompress to base_address
   d. For patches: store raw patch data for later application

4. VERIFY PE IMAGE
   a. Check for MZ signature (0x5A4D) at base_address
   b. If not valid PE and not a patch, loading fails

5. APPLY PATCHES (if applicable)
   a. Patch XEX headers using LZX delta
   b. Re-derive session keys for patched module
   c. Decrypt and apply image delta patches block by block
   d. Verify block hashes (SHA-1) at each step

6. PARSE PE HEADERS (LoadContinue)
   a. Verify DOS header (MZ), NT headers (PE\0\0)
   b. Verify Machine == POWERPCBE, Subsystem == XBOX
   c. Extract all PE sections (name, VA, size, flags)

7. SETUP MEMORY PROTECTION
   a. For each page_descriptor:
      - CODE / READONLY_DATA → Read-only
      - DATA → Read + Write
   b. Track low_address (first code page) and high_address (last code page)

8. RESOLVE IMPORTS
   a. Parse XEX_HEADER_IMPORT_LIBRARIES
   b. For each import library:
      - Parse string table for library names
      - Load dependent user modules if not already loaded
      - For each import record:
        * Variable (type 0): write resolved address to memory slot
        * Thunk (type 1): declare function, optionally rewrite PPC branch code

9. SETUP EXPORTS
   a. If security_info.export_table != 0: XEX export table is in memory
   b. If XEX_HEADER_EXPORTS_BY_NAME present: PE export directory is available

14. Integrity Verification (SHA-1)

SHA-1 is used throughout the format for data integrity:

Location What is Hashed Hash Location
Page descriptors Each page's data in memory xex2_page_descriptor.data_digest (20 bytes)
Security info Section data xex2_security_info.section_digest
Security info Import table xex2_security_info.import_table_digest
Security info Header data xex2_security_info.header_digest
Normal compression Each compressed block xex2_compressed_block_info.block_hash (chain)
Delta patches Each patch block xex2_compressed_block_info.block_hash
Import libraries Next import entry xex2_import_library.next_import_digest
Delta patches Source image xex2_opt_delta_patch_descriptor.digest_source

15. Key Source Files

File Purpose
src/xenia/kernel/util/xex2_info.h All XEX2 structure definitions, enums, and flags
src/xenia/cpu/xex_module.h XexModule class, SecurityInfoContext, ImportLibrary structures
src/xenia/cpu/xex_module.cc Main loading logic, AES keys, decryption, decompression dispatch
src/xenia/cpu/lzx.h / lzx.cc LZX decompression and delta patch application
src/xenia/base/pe_image.h PE (DOS/NT/Section) header structures
third_party/crypto/rijndael-alg-fst.c AES (Rijndael) cipher implementation
third_party/mspack/lzxd.c LZX decompression engine