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:
159
crates/xenia-kernel/src/state.rs
Normal file
159
crates/xenia-kernel/src/state.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use std::collections::HashMap;
|
||||
use xenia_cpu::PpcContext;
|
||||
use xenia_memory::GuestMemory;
|
||||
|
||||
use crate::objects::KernelObject;
|
||||
|
||||
/// Function signature for HLE kernel exports.
|
||||
pub type KernelExportFn = fn(&mut PpcContext, &mut GuestMemory, &mut KernelState);
|
||||
|
||||
/// Module identifier for kernel exports.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ModuleId {
|
||||
Xboxkrnl,
|
||||
Xam,
|
||||
Xbdm,
|
||||
}
|
||||
|
||||
/// Central kernel state tracking all guest OS state.
|
||||
pub struct KernelState {
|
||||
exports: HashMap<(ModuleId, u32), (&'static str, KernelExportFn)>,
|
||||
next_handle: u32,
|
||||
pub tls_slots: HashMap<u32, u64>,
|
||||
next_tls_index: u32,
|
||||
/// Kernel object table: handle → object
|
||||
pub objects: HashMap<u32, KernelObject>,
|
||||
/// Bump allocator for guest heap (NtAllocateVirtualMemory etc.)
|
||||
pub heap_cursor: u32,
|
||||
/// Stack allocator cursor for MmCreateKernelStack
|
||||
pub stack_cursor: u32,
|
||||
/// GPU command buffer address (set by VdGetSystemCommandBuffer)
|
||||
pub gpu_command_buffer: u32,
|
||||
/// Image base of the loaded XEX (for XexExecutableModuleHandle etc.)
|
||||
pub image_base: u32,
|
||||
/// Next thread ID
|
||||
pub next_thread_id: u32,
|
||||
}
|
||||
|
||||
impl KernelState {
|
||||
pub fn new() -> Self {
|
||||
let mut state = Self {
|
||||
exports: HashMap::new(),
|
||||
next_handle: 0x1000,
|
||||
tls_slots: HashMap::new(),
|
||||
next_tls_index: 0,
|
||||
objects: HashMap::new(),
|
||||
heap_cursor: 0x4000_0000, // Start of user heap region
|
||||
stack_cursor: 0x7100_0000, // Above main stack
|
||||
gpu_command_buffer: 0,
|
||||
image_base: 0,
|
||||
next_thread_id: 1,
|
||||
};
|
||||
crate::exports::register_exports(&mut state);
|
||||
crate::xam::register_exports(&mut state);
|
||||
state
|
||||
}
|
||||
|
||||
pub fn register_export(
|
||||
&mut self,
|
||||
module: ModuleId,
|
||||
ordinal: u32,
|
||||
name: &'static str,
|
||||
func: KernelExportFn,
|
||||
) {
|
||||
self.exports.insert((module, ordinal), (name, func));
|
||||
}
|
||||
|
||||
pub fn call_export(
|
||||
&mut self,
|
||||
module: ModuleId,
|
||||
ordinal: u32,
|
||||
ctx: &mut PpcContext,
|
||||
mem: &mut GuestMemory,
|
||||
) -> bool {
|
||||
if let Some(&(name, func)) = self.exports.get(&(module, ordinal)) {
|
||||
tracing::info!(
|
||||
"Kernel call: {:?}:{:#x} ({}) args=[{:#x}, {:#x}, {:#x}, {:#x}]",
|
||||
module, ordinal, name,
|
||||
ctx.gpr[3], ctx.gpr[4], ctx.gpr[5], ctx.gpr[6]
|
||||
);
|
||||
func(ctx, mem, self);
|
||||
tracing::info!(" -> returned {:#x}", ctx.gpr[3]);
|
||||
true
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Unimplemented kernel export: {:?}:{:#x}",
|
||||
module, ordinal
|
||||
);
|
||||
// Return 0 (STATUS_SUCCESS) by default for unimplemented calls
|
||||
ctx.gpr[3] = 0;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_name(&self, module: ModuleId, ordinal: u32) -> Option<&'static str> {
|
||||
self.exports.get(&(module, ordinal)).map(|&(name, _)| name)
|
||||
}
|
||||
|
||||
pub fn alloc_handle(&mut self) -> u32 {
|
||||
let h = self.next_handle;
|
||||
self.next_handle += 4;
|
||||
h
|
||||
}
|
||||
|
||||
pub fn alloc_handle_for(&mut self, obj: KernelObject) -> u32 {
|
||||
let h = self.alloc_handle();
|
||||
self.objects.insert(h, obj);
|
||||
h
|
||||
}
|
||||
|
||||
pub fn tls_get(&self, index: u32) -> u64 {
|
||||
self.tls_slots.get(&index).copied().unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn tls_set(&mut self, index: u32, value: u64) {
|
||||
self.tls_slots.insert(index, value);
|
||||
}
|
||||
|
||||
pub fn tls_alloc(&mut self) -> u32 {
|
||||
let idx = self.next_tls_index;
|
||||
self.next_tls_index += 1;
|
||||
idx
|
||||
}
|
||||
|
||||
/// Allocate guest memory from the heap bump allocator.
|
||||
/// Returns the base address of the allocated region.
|
||||
pub fn heap_alloc(&mut self, size: u32, mem: &mut GuestMemory) -> Option<u32> {
|
||||
let aligned_size = (size + 0xFFF) & !0xFFF; // Page-align
|
||||
let base = self.heap_cursor;
|
||||
if base.checked_add(aligned_size).is_none() || base + aligned_size > 0x6FFF_FFFF {
|
||||
return None;
|
||||
}
|
||||
let protect = xenia_memory::page_table::MemoryProtect::READ
|
||||
| xenia_memory::page_table::MemoryProtect::WRITE;
|
||||
if mem.alloc(base, aligned_size, protect).is_err() {
|
||||
return None;
|
||||
}
|
||||
self.heap_cursor += aligned_size;
|
||||
Some(base)
|
||||
}
|
||||
|
||||
/// Allocate a kernel stack.
|
||||
pub fn stack_alloc(&mut self, size: u32, mem: &mut GuestMemory) -> Option<u32> {
|
||||
let aligned_size = (size + 0xFFF) & !0xFFF;
|
||||
let base = self.stack_cursor;
|
||||
let protect = xenia_memory::page_table::MemoryProtect::READ
|
||||
| xenia_memory::page_table::MemoryProtect::WRITE;
|
||||
if mem.alloc(base, aligned_size, protect).is_err() {
|
||||
return None;
|
||||
}
|
||||
self.stack_cursor += aligned_size;
|
||||
Some(base + aligned_size) // Return top of stack
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KernelState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user