[Rust] Implement FPU/VMX128 opcodes, XEX LZX decompression, XISO browsing, and memory safety
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Successful in 2m5s
Orchestrator / Windows (x86-64) (push) Failing after 6m13s
Orchestrator / Linux (x86-64) (push) Failing after 20m59s
Orchestrator / Create Release (push) Has been skipped
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Successful in 2m5s
Orchestrator / Windows (x86-64) (push) Failing after 6m13s
Orchestrator / Linux (x86-64) (push) Failing after 20m59s
Orchestrator / Create Release (push) Has been skipped
Major additions to the xenia-rs Rust port: - CPU: ~170 new PPC opcode implementations (FPU, VMX128, 64-bit ALU, load/store variants) - XEX: Full LZX (normal) decompression pipeline with AES-128-CBC decryption via mspack FFI - XEX: Parse file format info, import libraries, and security info AES key from headers - VFS: Rewrite XISO disc image to use seek-based I/O (handles 7GB+ images without loading into memory) - App: Auto-detect ISO files and extract default.xex for all CLI commands - App: Add `info` and `browse` CLI subcommands - Kernel: Expand HLE exports from 14 to 40 stubs (memory, threading, TLS, I/O, video) - Memory: Add bounds checking on all guest memory accesses to prevent segfaults - Types: Add Vec128 array-based accessors (from_u32x4_array, from_f32x4_array, etc.) Tested against Project Sylpheed (USA) disc image - all four CLI commands (browse, info, disasm, exec) work correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,15 @@ pub fn register_exports(state: &mut KernelState) {
|
||||
state.register_export(Xboxkrnl, 0xBB, "NtAllocateVirtualMemory", nt_allocate_virtual_memory);
|
||||
state.register_export(Xboxkrnl, 0xBC, "NtFreeVirtualMemory", nt_free_virtual_memory);
|
||||
state.register_export(Xboxkrnl, 0xC4, "NtQueryVirtualMemory", nt_query_virtual_memory);
|
||||
state.register_export(Xboxkrnl, 0xB9, "MmAllocatePhysicalMemory", mm_allocate_physical_memory);
|
||||
state.register_export(Xboxkrnl, 0xBA, "MmAllocatePhysicalMemoryEx", mm_allocate_physical_memory_ex);
|
||||
|
||||
// Threading
|
||||
state.register_export(Xboxkrnl, 0x0C, "ExCreateThread", ex_create_thread);
|
||||
state.register_export(Xboxkrnl, 0x5F, "KeDelayExecutionThread", ke_delay_execution_thread);
|
||||
state.register_export(Xboxkrnl, 0x97, "KeSetAffinityThread", ke_set_affinity_thread);
|
||||
state.register_export(Xboxkrnl, 0x154, "KeTlsGetValue", ke_tls_get_value);
|
||||
state.register_export(Xboxkrnl, 0x155, "KeTlsSetValue", ke_tls_set_value);
|
||||
|
||||
// Sync
|
||||
state.register_export(Xboxkrnl, 0xC0, "NtCreateEvent", nt_create_event);
|
||||
@@ -23,24 +28,49 @@ pub fn register_exports(state: &mut KernelState) {
|
||||
state.register_export(Xboxkrnl, 0x6B, "KeWaitForSingleObject", ke_wait_for_single_object);
|
||||
state.register_export(Xboxkrnl, 0x53, "NtClose", nt_close);
|
||||
|
||||
// Spinlocks/IRQL
|
||||
state.register_export(Xboxkrnl, 0xB1, "KfAcquireSpinLock", kf_acquire_spin_lock);
|
||||
state.register_export(Xboxkrnl, 0xB4, "KfReleaseSpinLock", kf_release_spin_lock);
|
||||
state.register_export(Xboxkrnl, 0x85, "KeRaiseIrqlToDpcLevel", ke_raise_irql_to_dpc_level);
|
||||
state.register_export(Xboxkrnl, 0xB3, "KfLowerIrql", kf_lower_irql);
|
||||
|
||||
// Module
|
||||
state.register_export(Xboxkrnl, 0x195, "XexGetModuleHandle", xex_get_module_handle);
|
||||
state.register_export(Xboxkrnl, 0x197, "XexGetProcedureAddress", xex_get_procedure_address);
|
||||
|
||||
// Object
|
||||
state.register_export(Xboxkrnl, 0x110, "ObReferenceObjectByHandle", ob_reference_object_by_handle);
|
||||
|
||||
// Process/System
|
||||
state.register_export(Xboxkrnl, 0x66, "KeGetCurrentProcessType", ke_get_current_process_type);
|
||||
state.register_export(Xboxkrnl, 0x83, "KeQueryPerformanceFrequency", ke_query_performance_frequency);
|
||||
state.register_export(Xboxkrnl, 0x84, "KeQuerySystemTime", ke_query_system_time);
|
||||
state.register_export(Xboxkrnl, 0x10, "ExGetXConfigSetting", ex_get_xconfig_setting);
|
||||
|
||||
// RTL
|
||||
state.register_export(Xboxkrnl, 0x11A, "RtlInitAnsiString", rtl_init_ansi_string);
|
||||
state.register_export(Xboxkrnl, 0x12D, "RtlInitUnicodeString", rtl_init_unicode_string);
|
||||
state.register_export(Xboxkrnl, 0x127, "RtlFreeAnsiString", rtl_free_ansi_string);
|
||||
state.register_export(Xboxkrnl, 0x13B, "sprintf", stub_sprintf);
|
||||
|
||||
// I/O
|
||||
state.register_export(Xboxkrnl, 0xD2, "NtCreateFile", nt_create_file);
|
||||
state.register_export(Xboxkrnl, 0xF0, "NtReadFile", nt_read_file);
|
||||
state.register_export(Xboxkrnl, 0xE8, "NtQueryInformationFile", nt_query_information_file);
|
||||
state.register_export(Xboxkrnl, 0xE7, "NtQueryFullAttributesFile", nt_query_full_attributes_file);
|
||||
|
||||
// Video
|
||||
state.register_export(Xboxkrnl, 0x142, "VdGetCurrentDisplayGamma", vd_get_current_display_gamma);
|
||||
state.register_export(Xboxkrnl, 0x14B, "VdQueryVideoMode", vd_query_video_mode);
|
||||
state.register_export(Xboxkrnl, 0x1C2, "VdInitializeEngines", vd_initialize_engines);
|
||||
|
||||
// Debug
|
||||
state.register_export(Xboxkrnl, 0x166, "DbgPrint", dbg_print);
|
||||
}
|
||||
|
||||
// ===== Memory =====
|
||||
|
||||
fn nt_allocate_virtual_memory(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let _base_addr_ptr = ctx.gpr[3] as u32;
|
||||
let _size_ptr = ctx.gpr[4] as u32;
|
||||
// Stub: return success
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
@@ -52,18 +82,48 @@ fn nt_query_virtual_memory(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state:
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn mm_allocate_physical_memory(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = region, r4 = size, r5 = protect
|
||||
// Return a fake address in physical memory range
|
||||
ctx.gpr[3] = 0xA000_0000; // Fake physical allocation
|
||||
}
|
||||
|
||||
fn mm_allocate_physical_memory_ex(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = size, r4 = protect, r5 = min_addr, r6 = max_addr, r7 = alignment
|
||||
ctx.gpr[3] = 0xA000_0000; // Fake physical allocation
|
||||
}
|
||||
|
||||
// ===== Threading =====
|
||||
|
||||
fn ex_create_thread(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
// Write handle to output parameter (r3 = handle_ptr)
|
||||
tracing::info!("ExCreateThread: allocated handle {:#x}", handle);
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn ke_delay_execution_thread(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// In cooperative mode, this is where we'd yield to another thread
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn ke_set_affinity_thread(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = thread handle, r4 = affinity mask
|
||||
ctx.gpr[3] = 0; // Return previous affinity
|
||||
}
|
||||
|
||||
fn ke_tls_get_value(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let index = ctx.gpr[3] as u32;
|
||||
ctx.gpr[3] = state.tls_get(index);
|
||||
}
|
||||
|
||||
fn ke_tls_set_value(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let index = ctx.gpr[3] as u32;
|
||||
let value = ctx.gpr[4];
|
||||
state.tls_set(index, value);
|
||||
ctx.gpr[3] = 1; // TRUE = success
|
||||
}
|
||||
|
||||
// ===== Sync =====
|
||||
|
||||
fn nt_create_event(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let _handle = state.alloc_handle();
|
||||
ctx.gpr[3] = 0;
|
||||
@@ -74,25 +134,88 @@ fn ke_set_event(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut Kerne
|
||||
}
|
||||
|
||||
fn ke_wait_for_single_object(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Stub: return immediately as if signaled
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS (immediately signaled)
|
||||
}
|
||||
|
||||
fn nt_close(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xex_get_module_handle(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // Return NULL for now
|
||||
// ===== Spinlocks/IRQL =====
|
||||
|
||||
fn kf_acquire_spin_lock(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Return old IRQL (simulate DISPATCH_LEVEL = 2)
|
||||
ctx.gpr[3] = 0; // Previous IRQL (PASSIVE_LEVEL)
|
||||
}
|
||||
|
||||
fn kf_release_spin_lock(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = spin lock, r4 = old IRQL
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn ke_raise_irql_to_dpc_level(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // Return old IRQL
|
||||
}
|
||||
|
||||
fn kf_lower_irql(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Module =====
|
||||
|
||||
fn xex_get_module_handle(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // Return NULL
|
||||
}
|
||||
|
||||
fn xex_get_procedure_address(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = module_handle, r4 = ordinal, r5 = address_ptr
|
||||
let ordinal = ctx.gpr[4] as u32;
|
||||
tracing::warn!("XexGetProcedureAddress: ordinal {:#x} not found", ordinal);
|
||||
ctx.gpr[3] = 0xC000_0034; // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
}
|
||||
|
||||
// ===== Object =====
|
||||
|
||||
fn ob_reference_object_by_handle(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = handle, r4 = object_type, r5 = out_object_ptr
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
// ===== Process/System =====
|
||||
|
||||
fn ke_get_current_process_type(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 1; // PROC_USER (user mode process)
|
||||
}
|
||||
|
||||
fn ke_query_performance_frequency(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 50_000_000; // 50 MHz (Xbox 360 timebase frequency)
|
||||
}
|
||||
|
||||
fn ke_query_system_time(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
use xenia_memory::MemoryAccess;
|
||||
let time_ptr = ctx.gpr[3] as u32;
|
||||
if time_ptr != 0 {
|
||||
// Write a fake system time (Windows FILETIME format, 100ns intervals since 1601)
|
||||
// Use a fixed value so execution is deterministic
|
||||
let fake_time: u64 = 132_500_000_000_000_000; // ~2021
|
||||
mem.write_u32(time_ptr, (fake_time >> 32) as u32);
|
||||
mem.write_u32(time_ptr + 4, fake_time as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn ex_get_xconfig_setting(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = category, r4 = setting, r5 = buffer, r6 = buffer_size_ptr
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS (but writes nothing)
|
||||
}
|
||||
|
||||
// ===== RTL =====
|
||||
|
||||
fn rtl_init_ansi_string(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
use xenia_memory::MemoryAccess;
|
||||
let dest_ptr = ctx.gpr[3] as u32;
|
||||
let src_ptr = ctx.gpr[4] as u32;
|
||||
|
||||
if src_ptr != 0 {
|
||||
// Read string length
|
||||
let mut len: u16 = 0;
|
||||
let mut addr = src_ptr;
|
||||
while mem.read_u8(addr) != 0 {
|
||||
@@ -106,6 +229,78 @@ fn rtl_init_ansi_string(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mu
|
||||
}
|
||||
}
|
||||
|
||||
fn rtl_init_unicode_string(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
use xenia_memory::MemoryAccess;
|
||||
let dest_ptr = ctx.gpr[3] as u32;
|
||||
let src_ptr = ctx.gpr[4] as u32;
|
||||
|
||||
if src_ptr != 0 {
|
||||
// Count wide chars (2 bytes each, null-terminated)
|
||||
let mut len: u16 = 0;
|
||||
let mut addr = src_ptr;
|
||||
while mem.read_u16(addr) != 0 {
|
||||
len += 2;
|
||||
addr += 2;
|
||||
}
|
||||
// UNICODE_STRING: {Length, MaxLength, Buffer}
|
||||
mem.write_u16(dest_ptr, len);
|
||||
mem.write_u16(dest_ptr + 2, len + 2);
|
||||
mem.write_u32(dest_ptr + 4, src_ptr);
|
||||
} else {
|
||||
mem.write_u16(dest_ptr, 0);
|
||||
mem.write_u16(dest_ptr + 2, 0);
|
||||
mem.write_u32(dest_ptr + 4, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn rtl_free_ansi_string(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Stub: no-op (we don't track allocations yet)
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn stub_sprintf(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
use xenia_memory::MemoryAccess;
|
||||
// r3 = dest buffer, r4 = format string
|
||||
// Stub: just copy the format string as-is
|
||||
let dest = ctx.gpr[3] as u32;
|
||||
let fmt = ctx.gpr[4] as u32;
|
||||
if fmt != 0 && dest != 0 {
|
||||
let mut addr = fmt;
|
||||
let mut daddr = dest;
|
||||
loop {
|
||||
let c = mem.read_u8(addr);
|
||||
mem.write_u8(daddr, c);
|
||||
if c == 0 { break; }
|
||||
addr += 1;
|
||||
daddr += 1;
|
||||
}
|
||||
}
|
||||
ctx.gpr[3] = 0; // Return length (stub)
|
||||
}
|
||||
|
||||
// ===== I/O =====
|
||||
|
||||
fn nt_create_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
tracing::info!("NtCreateFile: allocated handle {:#x}", handle);
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn nt_read_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Stub: return end of file
|
||||
ctx.gpr[3] = 0xC000_0011; // STATUS_END_OF_FILE
|
||||
}
|
||||
|
||||
fn nt_query_information_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn nt_query_full_attributes_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0xC000_0034; // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
}
|
||||
|
||||
// ===== Video =====
|
||||
|
||||
fn vd_get_current_display_gamma(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
@@ -114,7 +309,6 @@ fn vd_query_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut
|
||||
use xenia_memory::MemoryAccess;
|
||||
let mode_ptr = ctx.gpr[3] as u32;
|
||||
if mode_ptr != 0 {
|
||||
// Write a basic video mode (1280x720)
|
||||
mem.write_u32(mode_ptr, 1280); // width
|
||||
mem.write_u32(mode_ptr + 4, 720); // height
|
||||
mem.write_u32(mode_ptr + 8, 0); // is_interlaced
|
||||
@@ -124,6 +318,13 @@ fn vd_query_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn vd_initialize_engines(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::info!("VdInitializeEngines called");
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Debug =====
|
||||
|
||||
fn dbg_print(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
use xenia_memory::MemoryAccess;
|
||||
let str_ptr = ctx.gpr[3] as u32;
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum ModuleId {
|
||||
pub struct KernelState {
|
||||
exports: HashMap<(ModuleId, u32), (&'static str, KernelExportFn)>,
|
||||
next_handle: u32,
|
||||
tls_slots: HashMap<u32, u64>,
|
||||
}
|
||||
|
||||
impl KernelState {
|
||||
@@ -24,6 +25,7 @@ impl KernelState {
|
||||
let mut state = Self {
|
||||
exports: HashMap::new(),
|
||||
next_handle: 0x1000,
|
||||
tls_slots: HashMap::new(),
|
||||
};
|
||||
crate::exports::register_exports(&mut state);
|
||||
state
|
||||
@@ -71,6 +73,14 @@ impl KernelState {
|
||||
self.next_handle += 4;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KernelState {
|
||||
|
||||
Reference in New Issue
Block a user