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:
11
crates/xenia-types/Cargo.toml
Normal file
11
crates/xenia-types/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "xenia-types"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
122
crates/xenia-types/src/endian.rs
Normal file
122
crates/xenia-types/src/endian.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
27
crates/xenia-types/src/error.rs
Normal file
27
crates/xenia-types/src/error.rs
Normal 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>;
|
||||
6
crates/xenia-types/src/lib.rs
Normal file
6
crates/xenia-types/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod endian;
|
||||
pub mod error;
|
||||
pub mod vec128;
|
||||
|
||||
pub use endian::Be;
|
||||
pub use vec128::Vec128;
|
||||
206
crates/xenia-types/src/vec128.rs
Normal file
206
crates/xenia-types/src/vec128.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user