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,122 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use std::marker::PhantomData;
/// Big-endian value wrapper matching `xe::be<T>` from the C++ codebase.
/// Stores the value in big-endian byte order and transparently converts
/// on access. Used for guest memory structures that are natively big-endian.
#[derive(Clone, Copy, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Be<T: BeSwap>(T::Bytes, PhantomData<T>);
impl<T: BeSwap> Be<T> {
pub fn new(val: T) -> Self {
Self(val.to_be_bytes(), PhantomData)
}
pub fn get(self) -> T {
T::from_be_bytes(self.0)
}
pub fn set(&mut self, val: T) {
self.0 = val.to_be_bytes();
}
pub fn raw_bytes(&self) -> &T::Bytes {
&self.0
}
}
impl<T: BeSwap + Default> Default for Be<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: BeSwap + fmt::Debug> fmt::Debug for Be<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(f)
}
}
impl<T: BeSwap + fmt::Display> fmt::Display for Be<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(f)
}
}
impl<T: BeSwap + PartialEq> PartialEq for Be<T> {
fn eq(&self, other: &Self) -> bool {
// Compare raw bytes for efficiency (same byte order)
self.0.as_ref() == other.0.as_ref()
}
}
impl<T: BeSwap + Eq> Eq for Be<T> {}
/// Trait for types that can be converted to/from big-endian byte representations.
pub trait BeSwap: Copy {
type Bytes: Copy + AsRef<[u8]> + serde::Serialize + for<'de> serde::Deserialize<'de>;
fn to_be_bytes(self) -> Self::Bytes;
fn from_be_bytes(bytes: Self::Bytes) -> Self;
}
macro_rules! impl_be_swap {
($t:ty) => {
impl BeSwap for $t {
type Bytes = [u8; std::mem::size_of::<$t>()];
fn to_be_bytes(self) -> Self::Bytes {
<$t>::to_be_bytes(self)
}
fn from_be_bytes(bytes: Self::Bytes) -> Self {
<$t>::from_be_bytes(bytes)
}
}
};
}
impl_be_swap!(u8);
impl_be_swap!(u16);
impl_be_swap!(u32);
impl_be_swap!(u64);
impl_be_swap!(i8);
impl_be_swap!(i16);
impl_be_swap!(i32);
impl_be_swap!(i64);
impl_be_swap!(f32);
impl_be_swap!(f64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_be_u32() {
let v = Be::<u32>::new(0x12345678);
assert_eq!(v.get(), 0x12345678);
assert_eq!(v.raw_bytes(), &[0x12, 0x34, 0x56, 0x78]);
}
#[test]
fn test_be_u16() {
let v = Be::<u16>::new(0xABCD);
assert_eq!(v.get(), 0xABCD);
assert_eq!(v.raw_bytes(), &[0xAB, 0xCD]);
}
#[test]
fn test_be_mutate() {
let mut v = Be::<u32>::new(1);
assert_eq!(v.get(), 1);
v.set(42);
assert_eq!(v.get(), 42);
}
#[test]
fn test_be_f32() {
let v = Be::<f32>::new(1.0);
assert_eq!(v.get(), 1.0);
// IEEE 754: 1.0f = 0x3F800000 => bytes [0x3F, 0x80, 0x00, 0x00]
assert_eq!(v.raw_bytes(), &[0x3F, 0x80, 0x00, 0x00]);
}
}

View File

@@ -0,0 +1,27 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum XeniaError {
#[error("Invalid XEX2 file: {0}")]
InvalidXex(String),
#[error("Invalid XISO file: {0}")]
InvalidXiso(String),
#[error("Memory error: {0}")]
Memory(String),
#[error("Unimplemented opcode: {0}")]
UnimplementedOpcode(String),
#[error("Unimplemented kernel export: module={module} ordinal={ordinal:#x}")]
UnimplementedExport { module: String, ordinal: u32 },
#[error("Invalid guest address: {0:#010x}")]
InvalidAddress(u32),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
pub type XeniaResult<T> = Result<T, XeniaError>;

View File

@@ -0,0 +1,6 @@
pub mod endian;
pub mod error;
pub mod vec128;
pub use endian::Be;
pub use vec128::Vec128;

View File

@@ -0,0 +1,206 @@
use serde::{Deserialize, Serialize};
use std::fmt;
/// 128-bit vector register type matching the Xbox 360's VMX128 registers.
/// Stored in big-endian byte order (matching guest memory layout).
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(C, align(16))]
pub struct Vec128 {
pub bytes: [u8; 16],
}
impl Vec128 {
pub const ZERO: Self = Self { bytes: [0; 16] };
pub fn from_u32x4(a: u32, b: u32, c: u32, d: u32) -> Self {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&a.to_be_bytes());
bytes[4..8].copy_from_slice(&b.to_be_bytes());
bytes[8..12].copy_from_slice(&c.to_be_bytes());
bytes[12..16].copy_from_slice(&d.to_be_bytes());
Self { bytes }
}
pub fn from_f32x4(a: f32, b: f32, c: f32, d: f32) -> Self {
Self::from_u32x4(a.to_bits(), b.to_bits(), c.to_bits(), d.to_bits())
}
/// Read the i-th u32 element (big-endian, 0-indexed).
pub fn u32x4(&self, i: usize) -> u32 {
let off = i * 4;
u32::from_be_bytes([
self.bytes[off],
self.bytes[off + 1],
self.bytes[off + 2],
self.bytes[off + 3],
])
}
/// Write the i-th u32 element (big-endian, 0-indexed).
pub fn set_u32x4(&mut self, i: usize, val: u32) {
let off = i * 4;
self.bytes[off..off + 4].copy_from_slice(&val.to_be_bytes());
}
/// Read the i-th f32 element (big-endian, 0-indexed).
pub fn f32x4(&self, i: usize) -> f32 {
f32::from_bits(self.u32x4(i))
}
/// Write the i-th f32 element (big-endian, 0-indexed).
pub fn set_f32x4(&mut self, i: usize, val: f32) {
self.set_u32x4(i, val.to_bits());
}
/// Read the i-th u16 element (big-endian, 0-indexed).
pub fn u16x8(&self, i: usize) -> u16 {
let off = i * 2;
u16::from_be_bytes([self.bytes[off], self.bytes[off + 1]])
}
/// Write the i-th u16 element (big-endian, 0-indexed).
pub fn set_u16x8(&mut self, i: usize, val: u16) {
let off = i * 2;
self.bytes[off..off + 2].copy_from_slice(&val.to_be_bytes());
}
/// Read the i-th u8 element (0-indexed).
pub fn u8x16(&self, i: usize) -> u8 {
self.bytes[i]
}
/// Write the i-th u8 element (0-indexed).
pub fn set_u8x16(&mut self, i: usize, val: u8) {
self.bytes[i] = val;
}
/// Read as two u64 values (big-endian).
pub fn u64x2(&self, i: usize) -> u64 {
let off = i * 8;
u64::from_be_bytes([
self.bytes[off],
self.bytes[off + 1],
self.bytes[off + 2],
self.bytes[off + 3],
self.bytes[off + 4],
self.bytes[off + 5],
self.bytes[off + 6],
self.bytes[off + 7],
])
}
pub fn set_u64x2(&mut self, i: usize, val: u64) {
let off = i * 8;
self.bytes[off..off + 8].copy_from_slice(&val.to_be_bytes());
}
/// Get all 4 u32 elements as an array.
pub fn as_u32x4(&self) -> [u32; 4] {
[self.u32x4(0), self.u32x4(1), self.u32x4(2), self.u32x4(3)]
}
/// Get all 4 f32 elements as an array.
pub fn as_f32x4(&self) -> [f32; 4] {
[self.f32x4(0), self.f32x4(1), self.f32x4(2), self.f32x4(3)]
}
/// Get all 8 u16 elements as an array.
pub fn as_u16x8(&self) -> [u16; 8] {
[
self.u16x8(0), self.u16x8(1), self.u16x8(2), self.u16x8(3),
self.u16x8(4), self.u16x8(5), self.u16x8(6), self.u16x8(7),
]
}
/// Get all 16 bytes as an array.
pub fn as_bytes(&self) -> [u8; 16] {
self.bytes
}
/// Create from a byte array.
pub fn from_bytes(bytes: [u8; 16]) -> Self {
Self { bytes }
}
/// Create from a u32 array (big-endian elements).
pub fn from_u32x4_array(arr: [u32; 4]) -> Self {
Self::from_u32x4(arr[0], arr[1], arr[2], arr[3])
}
/// Create from an f32 array (big-endian elements).
pub fn from_f32x4_array(arr: [f32; 4]) -> Self {
Self::from_f32x4(arr[0], arr[1], arr[2], arr[3])
}
/// Create from a u16 array (big-endian elements).
pub fn from_u16x8_array(arr: [u16; 8]) -> Self {
let mut v = Self::ZERO;
for i in 0..8 { v.set_u16x8(i, arr[i]); }
v
}
}
impl Default for Vec128 {
fn default() -> Self {
Self::ZERO
}
}
impl fmt::Debug for Vec128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Vec128({:08X}_{:08X}_{:08X}_{:08X})",
self.u32x4(0),
self.u32x4(1),
self.u32x4(2),
self.u32x4(3),
)
}
}
impl fmt::Display for Vec128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u32x4_roundtrip() {
let v = Vec128::from_u32x4(0xDEADBEEF, 0xCAFEBABE, 0x12345678, 0x9ABCDEF0);
assert_eq!(v.u32x4(0), 0xDEADBEEF);
assert_eq!(v.u32x4(1), 0xCAFEBABE);
assert_eq!(v.u32x4(2), 0x12345678);
assert_eq!(v.u32x4(3), 0x9ABCDEF0);
}
#[test]
fn test_f32x4_roundtrip() {
let v = Vec128::from_f32x4(1.0, -2.5, 3.14, 0.0);
assert_eq!(v.f32x4(0), 1.0);
assert_eq!(v.f32x4(1), -2.5);
assert!((v.f32x4(2) - 3.14).abs() < f32::EPSILON);
assert_eq!(v.f32x4(3), 0.0);
}
#[test]
fn test_u16x8() {
let v = Vec128::from_u32x4(0x00010002, 0x00030004, 0x00050006, 0x00070008);
assert_eq!(v.u16x8(0), 0x0001);
assert_eq!(v.u16x8(1), 0x0002);
assert_eq!(v.u16x8(6), 0x0007);
assert_eq!(v.u16x8(7), 0x0008);
}
#[test]
fn test_zero() {
let v = Vec128::ZERO;
for i in 0..4 {
assert_eq!(v.u32x4(i), 0);
}
}
}