Initial commit: xenia-rs workspace for Xbox 360 RE

Rust reimplementation of the xenia Xbox 360 emulator targeting reverse-
engineering and preservation, initially scoped to Project Sylpheed.

Includes:
- XEX2 loader (LZX decompression, AES decryption, PE parsing)
- XISO / XGD2 disc image VFS
- PPC interpreter with 200+ opcodes and VMX128 decoding
- Static analyzer: functions, cross-references, labels, asm + SQLite output
- HLE kernel covering the xboxkrnl/xam subset used by Sylpheed init
- Debugger with in-memory and SQLite-backed execution tracing
- `xenia-rs` CLI with extract/dis/exec commands that produce cumulative,
  superset SQLite databases and opt-in instruction/import/branch traces

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-04-16 23:11:49 +02:00
commit c694bb3f43
63 changed files with 13456 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "xenia-vfs"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
xenia-types = { workspace = true }
tracing = { workspace = true }
byteorder = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }

View File

@@ -0,0 +1,54 @@
use crate::{VfsDevice, VfsEntry, VfsError};
use std::path::{Path, PathBuf};
/// Host filesystem pass-through device.
pub struct HostPathDevice {
name: String,
root: PathBuf,
}
impl HostPathDevice {
pub fn new(name: impl Into<String>, root: impl AsRef<Path>) -> Self {
Self {
name: name.into(),
root: root.as_ref().to_path_buf(),
}
}
}
impl VfsDevice for HostPathDevice {
fn name(&self) -> &str {
&self.name
}
fn list_root(&self) -> Result<Vec<VfsEntry>, VfsError> {
let mut entries = Vec::new();
for entry in std::fs::read_dir(&self.root)? {
let entry = entry?;
let metadata = entry.metadata()?;
entries.push(VfsEntry {
name: entry.file_name().to_string_lossy().into_owned(),
is_directory: metadata.is_dir(),
size: metadata.len(),
offset: 0,
});
}
Ok(entries)
}
fn read_file(&self, path: &str) -> Result<Vec<u8>, VfsError> {
let full_path = self.root.join(path);
std::fs::read(&full_path).map_err(VfsError::from)
}
fn stat(&self, path: &str) -> Result<VfsEntry, VfsError> {
let full_path = self.root.join(path);
let metadata = std::fs::metadata(&full_path)?;
Ok(VfsEntry {
name: path.to_string(),
is_directory: metadata.is_dir(),
size: metadata.len(),
offset: 0,
})
}
}

View File

@@ -0,0 +1,185 @@
use crate::{VfsDevice, VfsEntry, VfsError};
use std::io::{Read, Seek, SeekFrom};
/// XISO disc image device. Parses Xbox 360 disc images (GDFX/XISO format).
pub struct DiscImageDevice {
name: String,
path: std::path::PathBuf,
game_offset: u64,
/// Cached root directory buffer (typically small, a few KB).
root_buffer: Vec<u8>,
}
/// XISO sector size
pub const SECTOR_SIZE: u64 = 0x800;
/// GDFX magic string
const GDFX_MAGIC: &[u8; 20] = b"MICROSOFT*XBOX*MEDIA";
/// File attribute: directory
const FILE_ATTRIBUTE_DIRECTORY: u8 = 0x10;
/// Known game partition offsets to try
const LIKELY_OFFSETS: &[u64] = &[
0x0000_0000,
0x0000_FB20,
0x0002_0600,
0x0208_0000,
0x0FD9_0000,
];
impl DiscImageDevice {
pub fn open(name: impl Into<String>, path: &std::path::Path) -> Result<Self, VfsError> {
let mut file = std::fs::File::open(path)?;
// Find the game partition by locating the GDFX magic at sector 32
let mut game_offset = 0u64;
let mut magic_found = false;
let mut magic_buf = [0u8; 20];
for &offset in LIKELY_OFFSETS {
let magic_pos = offset + 32 * SECTOR_SIZE;
if file.seek(SeekFrom::Start(magic_pos)).is_ok()
&& file.read_exact(&mut magic_buf).is_ok()
&& magic_buf == *GDFX_MAGIC
{
game_offset = offset;
magic_found = true;
break;
}
}
if !magic_found {
return Err(VfsError::InvalidFormat(
"GDFX magic not found - not a valid XISO disc image".into(),
));
}
// Read root directory info from sector 32 header
let fs_ptr = game_offset + 32 * SECTOR_SIZE;
file.seek(SeekFrom::Start(fs_ptr + 20))?;
let mut buf4 = [0u8; 4];
file.read_exact(&mut buf4)?;
let root_sector = u32::from_le_bytes(buf4) as u64;
file.read_exact(&mut buf4)?;
let root_size = u32::from_le_bytes(buf4) as u64;
let root_byte_offset = game_offset + root_sector * SECTOR_SIZE;
// Read the root directory buffer into memory (typically small)
file.seek(SeekFrom::Start(root_byte_offset))?;
let mut root_buffer = vec![0u8; root_size as usize];
file.read_exact(&mut root_buffer)?;
Ok(Self {
name: name.into(),
path: path.to_path_buf(),
game_offset,
root_buffer,
})
}
/// Read all directory entries from the root directory tree.
fn read_entries(&self) -> Vec<VfsEntry> {
let mut entries = Vec::new();
self.read_entry(&self.root_buffer, 0, &mut entries);
entries
}
/// Recursively read a directory entry from the binary tree structure.
fn read_entry(&self, buffer: &[u8], ordinal: u16, entries: &mut Vec<VfsEntry>) {
let p = ordinal as usize * 4;
if p + 14 > buffer.len() {
return;
}
let node_l = u16::from_le_bytes([buffer[p], buffer[p + 1]]);
let node_r = u16::from_le_bytes([buffer[p + 2], buffer[p + 3]]);
let sector = u32::from_le_bytes([buffer[p + 4], buffer[p + 5], buffer[p + 6], buffer[p + 7]]) as u64;
let length = u32::from_le_bytes([buffer[p + 8], buffer[p + 9], buffer[p + 10], buffer[p + 11]]) as u64;
let attributes = buffer[p + 12];
let name_length = buffer[p + 13] as usize;
if p + 14 + name_length > buffer.len() {
return;
}
// Traverse left subtree first (smaller names)
if node_l != 0 && node_l != 0xFFFF {
self.read_entry(buffer, node_l, entries);
}
// Read this entry's name
let name = String::from_utf8_lossy(&buffer[p + 14..p + 14 + name_length]).to_string();
let is_directory = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
let file_offset = self.game_offset + sector * SECTOR_SIZE;
entries.push(VfsEntry {
name,
is_directory,
size: length,
offset: file_offset,
});
// Traverse right subtree (larger names)
if node_r != 0 && node_r != 0xFFFF {
self.read_entry(buffer, node_r, entries);
}
}
}
impl VfsDevice for DiscImageDevice {
fn name(&self) -> &str {
&self.name
}
fn list_root(&self) -> Result<Vec<VfsEntry>, VfsError> {
Ok(self.read_entries())
}
fn read_file(&self, path: &str) -> Result<Vec<u8>, VfsError> {
let entries = self.read_entries();
let entry = entries.iter()
.find(|e| e.name.eq_ignore_ascii_case(path) && !e.is_directory)
.ok_or_else(|| VfsError::NotFound(path.to_string()))?;
let offset = entry.offset;
let size = entry.size as usize;
// Read from file using seek
let mut file = std::fs::File::open(&self.path)?;
let file_len = file.seek(SeekFrom::End(0))?;
if offset + size as u64 > file_len {
return Err(VfsError::NotFound(format!(
"File data extends past end of image: {} (offset={:#x}, size={:#x}, image_len={:#x})",
path, offset, size, file_len
)));
}
file.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; size];
let bytes_read = file.read(&mut buf)?;
if bytes_read < size {
// Try reading the rest
let mut total = bytes_read;
while total < size {
let n = file.read(&mut buf[total..])?;
if n == 0 {
return Err(VfsError::NotFound(format!(
"Short read: got {} of {} bytes for {}",
total, size, path
)));
}
total += n;
}
}
Ok(buf)
}
fn stat(&self, path: &str) -> Result<VfsEntry, VfsError> {
let entries = self.read_entries();
entries.into_iter()
.find(|e| e.name.eq_ignore_ascii_case(path))
.ok_or_else(|| VfsError::NotFound(path.to_string()))
}
}

View File

@@ -0,0 +1,33 @@
pub mod device;
pub mod disc_image;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VfsError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid format: {0}")]
InvalidFormat(String),
#[error("File not found: {0}")]
NotFound(String),
}
/// A virtual filesystem entry (file or directory).
#[derive(Debug)]
pub struct VfsEntry {
pub name: String,
pub is_directory: bool,
pub size: u64,
pub offset: u64,
}
/// Trait for VFS device implementations (XISO, STFS, host path, etc.)
pub trait VfsDevice: Send + Sync {
fn name(&self) -> &str;
fn list_root(&self) -> Result<Vec<VfsEntry>, VfsError>;
fn read_file(&self, path: &str) -> Result<Vec<u8>, VfsError>;
fn stat(&self, path: &str) -> Result<VfsEntry, VfsError>;
}