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:
13
crates/xenia-kernel/Cargo.toml
Normal file
13
crates/xenia-kernel/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "xenia-kernel"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
xenia-types = { workspace = true }
|
||||
xenia-memory = { workspace = true }
|
||||
xenia-cpu = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
763
crates/xenia-kernel/src/exports.rs
Normal file
763
crates/xenia-kernel/src/exports.rs
Normal file
@@ -0,0 +1,763 @@
|
||||
//! HLE kernel export implementations (xboxkrnl.exe).
|
||||
//! Each export mirrors a function from xboxkrnl_table.inc.
|
||||
|
||||
use crate::objects::KernelObject;
|
||||
use crate::state::{KernelState, ModuleId};
|
||||
use xenia_cpu::PpcContext;
|
||||
use xenia_memory::{GuestMemory, MemoryAccess};
|
||||
|
||||
pub fn register_exports(state: &mut KernelState) {
|
||||
use ModuleId::Xboxkrnl;
|
||||
|
||||
// Debug
|
||||
state.register_export(Xboxkrnl, 0x01, "DbgBreakPoint", dbg_break_point);
|
||||
state.register_export(Xboxkrnl, 0x03, "DbgPrint", dbg_print);
|
||||
|
||||
// ExCreateThread and friends
|
||||
state.register_export(Xboxkrnl, 0x0D, "ExCreateThread", ex_create_thread);
|
||||
state.register_export(Xboxkrnl, 0x10, "ExGetXConfigSetting", ex_get_xconfig_setting);
|
||||
state.register_export(Xboxkrnl, 0x15, "ExRegisterTitleTerminateNotification", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x19, "ExTerminateThread", ex_terminate_thread);
|
||||
|
||||
// Hal
|
||||
state.register_export(Xboxkrnl, 0x28, "HalReturnToFirmware", hal_return_to_firmware);
|
||||
|
||||
// I/O
|
||||
state.register_export(Xboxkrnl, 0x3C, "IoDismountVolumeByFileHandle", stub_success);
|
||||
|
||||
// Ke* Threading/Sync
|
||||
state.register_export(Xboxkrnl, 0x4D, "KeAcquireSpinLockAtRaisedIrql", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x52, "KeBugCheck", ke_bug_check);
|
||||
state.register_export(Xboxkrnl, 0x53, "KeBugCheckEx", ke_bug_check_ex);
|
||||
state.register_export(Xboxkrnl, 0x5A, "KeDelayExecutionThread", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x5D, "KeEnableFpuExceptions", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x5F, "KeEnterCriticalRegion", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x66, "KeGetCurrentProcessType", ke_get_current_process_type);
|
||||
state.register_export(Xboxkrnl, 0x6B, "KeLockL2", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x6C, "KeUnlockL2", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x74, "KeInitializeSemaphore", ke_initialize_semaphore);
|
||||
state.register_export(Xboxkrnl, 0x7D, "KeLeaveCriticalRegion", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x81, "KeQueryBasePriorityThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x83, "KeQueryPerformanceFrequency", ke_query_performance_frequency);
|
||||
state.register_export(Xboxkrnl, 0x84, "KeQuerySystemTime", ke_query_system_time);
|
||||
state.register_export(Xboxkrnl, 0x85, "KeRaiseIrqlToDpcLevel", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x88, "KeReleaseSemaphore", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x89, "KeReleaseSpinLockFromRaisedIrql", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x8F, "KeResetEvent", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x92, "KeResumeThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x97, "KeSetAffinityThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x99, "KeSetBasePriorityThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x9B, "KeSetCurrentStackPointers", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x9D, "KeSetEvent", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0xAE, "KeTryToAcquireSpinLockAtRaisedIrql", ke_try_acquire_spinlock);
|
||||
state.register_export(Xboxkrnl, 0xAF, "KeWaitForMultipleObjects", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xB0, "KeWaitForSingleObject", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xB1, "KfAcquireSpinLock", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0xB3, "KfLowerIrql", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xB4, "KfReleaseSpinLock", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0152, "KeTlsAlloc", ke_tls_alloc);
|
||||
state.register_export(Xboxkrnl, 0x0153, "KeTlsFree", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0154, "KeTlsGetValue", ke_tls_get_value);
|
||||
state.register_export(Xboxkrnl, 0x0155, "KeTlsSetValue", ke_tls_set_value);
|
||||
state.register_export(Xboxkrnl, 0x01DF, "KiApcNormalRoutineNop", stub_success);
|
||||
|
||||
// Memory
|
||||
state.register_export(Xboxkrnl, 0xBA, "MmAllocatePhysicalMemoryEx", mm_allocate_physical_memory_ex);
|
||||
state.register_export(Xboxkrnl, 0xBB, "MmCreateKernelStack", mm_create_kernel_stack);
|
||||
state.register_export(Xboxkrnl, 0xBC, "MmDeleteKernelStack", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xBD, "MmFreePhysicalMemory", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xBE, "MmGetPhysicalAddress", mm_get_physical_address);
|
||||
state.register_export(Xboxkrnl, 0xC4, "MmQueryAddressProtect", mm_query_address_protect);
|
||||
state.register_export(Xboxkrnl, 0xC6, "MmQueryStatistics", mm_query_statistics);
|
||||
|
||||
// Nt*
|
||||
state.register_export(Xboxkrnl, 0xCC, "NtAllocateVirtualMemory", nt_allocate_virtual_memory);
|
||||
state.register_export(Xboxkrnl, 0xCD, "NtCancelTimer", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xCE, "NtClearEvent", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xCF, "NtClose", nt_close);
|
||||
state.register_export(Xboxkrnl, 0xD1, "NtCreateEvent", nt_create_event);
|
||||
state.register_export(Xboxkrnl, 0xD2, "NtCreateFile", nt_create_file);
|
||||
state.register_export(Xboxkrnl, 0xD5, "NtCreateSemaphore", nt_create_semaphore);
|
||||
state.register_export(Xboxkrnl, 0xD7, "NtCreateTimer", nt_create_timer);
|
||||
state.register_export(Xboxkrnl, 0xD9, "NtDeviceIoControlFile", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xDA, "NtDuplicateObject", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xDB, "NtFlushBuffersFile", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xDC, "NtFreeVirtualMemory", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xDF, "NtOpenFile", nt_open_file);
|
||||
state.register_export(Xboxkrnl, 0xE4, "NtQueryDirectoryFile", nt_query_directory_file);
|
||||
state.register_export(Xboxkrnl, 0xE7, "NtQueryFullAttributesFile", nt_query_full_attributes_file);
|
||||
state.register_export(Xboxkrnl, 0xE8, "NtQueryInformationFile", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xEE, "NtQueryVirtualMemory", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xEF, "NtQueryVolumeInformationFile", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xF0, "NtReadFile", nt_read_file);
|
||||
state.register_export(Xboxkrnl, 0xF3, "NtReleaseSemaphore", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0xF5, "NtResumeThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0xF6, "NtSetEvent", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xF7, "NtSetInformationFile", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xFA, "NtSetTimerEx", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xFC, "NtSuspendThread", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0xFD, "NtWaitForSingleObjectEx", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xFE, "NtWaitForMultipleObjectsEx", stub_success);
|
||||
state.register_export(Xboxkrnl, 0xFF, "NtWriteFile", nt_write_file);
|
||||
state.register_export(Xboxkrnl, 0x0101, "NtYieldExecution", stub_success);
|
||||
|
||||
// Object
|
||||
state.register_export(Xboxkrnl, 0x0103, "ObCreateSymbolicLink", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0104, "ObDeleteSymbolicLink", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0105, "ObDereferenceObject", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x010B, "ObLookupThreadByThreadId", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x010E, "ObOpenObjectByPointer", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0110, "ObReferenceObjectByHandle", stub_success);
|
||||
|
||||
// RTL
|
||||
state.register_export(Xboxkrnl, 0x0119, "RtlCaptureContext", rtl_capture_context);
|
||||
state.register_export(Xboxkrnl, 0x011B, "RtlCompareMemoryUlong", rtl_compare_memory_ulong);
|
||||
state.register_export(Xboxkrnl, 0x0125, "RtlEnterCriticalSection", rtl_enter_critical_section);
|
||||
state.register_export(Xboxkrnl, 0x0126, "RtlFillMemoryUlong", rtl_fill_memory_ulong);
|
||||
state.register_export(Xboxkrnl, 0x0127, "RtlFreeAnsiString", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x012B, "RtlImageXexHeaderField", rtl_image_xex_header_field);
|
||||
state.register_export(Xboxkrnl, 0x012C, "RtlInitAnsiString", rtl_init_ansi_string);
|
||||
state.register_export(Xboxkrnl, 0x012D, "RtlInitUnicodeString", rtl_init_unicode_string);
|
||||
state.register_export(Xboxkrnl, 0x012E, "RtlInitializeCriticalSection", rtl_initialize_critical_section);
|
||||
state.register_export(Xboxkrnl, 0x012F, "RtlInitializeCriticalSectionAndSpinCount", rtl_initialize_critical_section);
|
||||
state.register_export(Xboxkrnl, 0x0130, "RtlLeaveCriticalSection", rtl_leave_critical_section);
|
||||
state.register_export(Xboxkrnl, 0x0133, "RtlMultiByteToUnicodeN", rtl_multi_byte_to_unicode_n);
|
||||
state.register_export(Xboxkrnl, 0x0135, "RtlNtStatusToDosError", rtl_nt_status_to_dos_error);
|
||||
state.register_export(Xboxkrnl, 0x0136, "RtlRaiseException", rtl_raise_exception);
|
||||
state.register_export(Xboxkrnl, 0x013B, "sprintf", stub_sprintf);
|
||||
state.register_export(Xboxkrnl, 0x013F, "RtlTimeFieldsToTime", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0140, "RtlTimeToTimeFields", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0141, "RtlTryEnterCriticalSection", rtl_try_enter_critical_section);
|
||||
state.register_export(Xboxkrnl, 0x0142, "RtlUnicodeStringToAnsiString", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0143, "RtlUnicodeToMultiByteN", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0147, "RtlUnwind", rtl_unwind);
|
||||
state.register_export(Xboxkrnl, 0x014D, "_vsnprintf", stub_vsnprintf);
|
||||
|
||||
// Stfs
|
||||
state.register_export(Xboxkrnl, 0x0259, "StfsCreateDevice", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x025A, "StfsControlDevice", stub_success);
|
||||
|
||||
// Video
|
||||
state.register_export(Xboxkrnl, 0x01B1, "VdCallGraphicsNotificationRoutines", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01B4, "VdEnableDisableClockGating", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01B6, "VdEnableRingBufferRPtrWriteBack", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01B9, "VdGetCurrentDisplayGamma", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x01BA, "VdGetCurrentDisplayInformation", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01BD, "VdGetSystemCommandBuffer", vd_get_system_command_buffer);
|
||||
state.register_export(Xboxkrnl, 0x01C2, "VdInitializeEngines", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01C3, "VdInitializeRingBuffer", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01C5, "VdInitializeScalerCommandBuffer", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01C6, "VdIsHSIOTrainingSucceeded", vd_is_hsio_training_succeeded);
|
||||
state.register_export(Xboxkrnl, 0x01C7, "VdPersistDisplay", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01C9, "VdQueryVideoFlags", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x01CA, "VdQueryVideoMode", vd_query_video_mode);
|
||||
state.register_export(Xboxkrnl, 0x0269, "VdRetrainEDRAM", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x026A, "VdRetrainEDRAMWorker", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01D3, "VdSetDisplayMode", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01D5, "VdSetGraphicsInterruptCallback", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01D9, "VdSetSystemCommandBufferGpuIdentifierAddress", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01DC, "VdShutdownEngines", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x025B, "VdSwap", vd_swap);
|
||||
|
||||
// Audio
|
||||
state.register_export(Xboxkrnl, 0x01F3, "XAudioRegisterRenderDriverClient", xaudio_register_render_driver);
|
||||
state.register_export(Xboxkrnl, 0x01F4, "XAudioUnregisterRenderDriverClient", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01F5, "XAudioSubmitRenderDriverFrame", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x01F7, "XAudioGetVoiceCategoryVolumeChangeMask", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x01F8, "XAudioGetVoiceCategoryVolume", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0224, "XMACreateContext", xma_create_context);
|
||||
state.register_export(Xboxkrnl, 0x0226, "XMAReleaseContext", stub_success);
|
||||
|
||||
// Crypto
|
||||
state.register_export(Xboxkrnl, 0x0192, "XeCryptSha", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0256, "XeKeysConsolePrivateKeySign", stub_success);
|
||||
state.register_export(Xboxkrnl, 0x0257, "XeKeysConsoleSignatureVerification", stub_success);
|
||||
|
||||
// Xex module
|
||||
state.register_export(Xboxkrnl, 0x0194, "XexCheckExecutablePrivilege", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x0195, "XexGetModuleHandle", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x0197, "XexGetProcedureAddress", xex_get_procedure_address);
|
||||
|
||||
// Exception handling
|
||||
state.register_export(Xboxkrnl, 0x01A5, "__C_specific_handler", c_specific_handler);
|
||||
}
|
||||
|
||||
// ===== Generic stubs =====
|
||||
|
||||
fn stub_success(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn stub_return_zero(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Debug =====
|
||||
|
||||
fn dbg_break_point(_ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("DbgBreakPoint hit");
|
||||
}
|
||||
|
||||
fn dbg_print(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let str_ptr = ctx.gpr[3] as u32;
|
||||
if str_ptr != 0 {
|
||||
let s = read_cstring(mem, str_ptr);
|
||||
tracing::info!("DbgPrint: {}", s);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Threading =====
|
||||
|
||||
fn ex_create_thread(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = handle_ptr, r4 = stack_size, r5 = thread_id_ptr, r6 = xapi_startup
|
||||
// r7 = start_address, r8 = start_context, r9 = creation_flags
|
||||
let handle_ptr = ctx.gpr[3] as u32;
|
||||
let thread_id_ptr = ctx.gpr[5] as u32;
|
||||
|
||||
let tid = state.next_thread_id;
|
||||
state.next_thread_id += 1;
|
||||
let handle = state.alloc_handle_for(KernelObject::Thread { id: tid });
|
||||
|
||||
if handle_ptr != 0 {
|
||||
mem.write_u32(handle_ptr, handle);
|
||||
}
|
||||
if thread_id_ptr != 0 {
|
||||
mem.write_u32(thread_id_ptr, tid);
|
||||
}
|
||||
tracing::info!("ExCreateThread: handle={:#x} tid={}", handle, tid);
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
fn ex_terminate_thread(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::info!("ExTerminateThread: exit_status={:#x}", ctx.gpr[3]);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn hal_return_to_firmware(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("HalReturnToFirmware: reason={:#x}", ctx.gpr[3]);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Ke* =====
|
||||
|
||||
fn ke_bug_check(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::error!("KeBugCheck: code={:#x}", ctx.gpr[3]);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn ke_bug_check_ex(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::error!("KeBugCheckEx: code={:#x} p1={:#x} p2={:#x} p3={:#x}",
|
||||
ctx.gpr[3], ctx.gpr[4], ctx.gpr[5], ctx.gpr[6]);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn ke_get_current_process_type(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 1; // PROC_USER
|
||||
}
|
||||
|
||||
fn ke_query_performance_frequency(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 50_000_000; // 50 MHz
|
||||
}
|
||||
|
||||
fn ke_query_system_time(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let time_ptr = ctx.gpr[3] as u32;
|
||||
if time_ptr != 0 {
|
||||
let fake_time: u64 = 132_500_000_000_000_000; // ~2021 FILETIME
|
||||
mem.write_u32(time_ptr, (fake_time >> 32) as u32);
|
||||
mem.write_u32(time_ptr + 4, fake_time as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn ke_initialize_semaphore(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = semaphore_ptr, r4 = count, r5 = limit
|
||||
let sem_ptr = ctx.gpr[3] as u32;
|
||||
if sem_ptr != 0 {
|
||||
// Zero-init the KSEMAPHORE structure (0x14 bytes)
|
||||
for i in (0..0x14).step_by(4) {
|
||||
mem.write_u32(sem_ptr + i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ke_try_acquire_spinlock(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 1; // TRUE (acquired successfully in single-threaded mode)
|
||||
}
|
||||
|
||||
fn ke_tls_alloc(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
ctx.gpr[3] = state.tls_alloc() as u64;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn ex_get_xconfig_setting(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS (writes nothing)
|
||||
}
|
||||
|
||||
// ===== Memory =====
|
||||
|
||||
fn nt_allocate_virtual_memory(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = base_addr_ptr (in/out), r4 = region_size_ptr (in/out)
|
||||
// r5 = alloc_type, r6 = protect
|
||||
let base_ptr = ctx.gpr[3] as u32;
|
||||
let size_ptr = ctx.gpr[4] as u32;
|
||||
|
||||
let requested_base = mem.read_u32(base_ptr);
|
||||
let requested_size = mem.read_u32(size_ptr);
|
||||
|
||||
let aligned_size = (requested_size + 0xFFF) & !0xFFF;
|
||||
if aligned_size == 0 {
|
||||
ctx.gpr[3] = 0xC000_0010; // STATUS_INVALID_PARAMETER
|
||||
return;
|
||||
}
|
||||
|
||||
let base = if requested_base != 0 {
|
||||
// Try to allocate at the requested address
|
||||
let protect = xenia_memory::page_table::MemoryProtect::READ
|
||||
| xenia_memory::page_table::MemoryProtect::WRITE;
|
||||
if mem.alloc(requested_base, aligned_size, protect).is_ok() {
|
||||
requested_base
|
||||
} else {
|
||||
// Already allocated? Treat as success (common for re-commit)
|
||||
requested_base
|
||||
}
|
||||
} else {
|
||||
// Allocate from heap
|
||||
match state.heap_alloc(aligned_size, mem) {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
tracing::warn!("NtAllocateVirtualMemory: heap exhausted (size={:#x})", aligned_size);
|
||||
ctx.gpr[3] = 0xC000_0017; // STATUS_NO_MEMORY
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mem.write_u32(base_ptr, base);
|
||||
mem.write_u32(size_ptr, aligned_size);
|
||||
tracing::info!("NtAllocateVirtualMemory: base={:#010x} size={:#x}", base, aligned_size);
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS
|
||||
}
|
||||
|
||||
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
|
||||
let size = ctx.gpr[3] as u32;
|
||||
match state.heap_alloc(size, mem) {
|
||||
Some(addr) => ctx.gpr[3] = addr as u64,
|
||||
None => ctx.gpr[3] = 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn mm_create_kernel_stack(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = stack_size, r4 = reserved
|
||||
let size = std::cmp::max(ctx.gpr[3] as u32, 0x4000); // Min 16KB
|
||||
match state.stack_alloc(size, mem) {
|
||||
Some(top) => {
|
||||
tracing::info!("MmCreateKernelStack: top={:#010x} size={:#x}", top, size);
|
||||
ctx.gpr[3] = top as u64;
|
||||
}
|
||||
None => ctx.gpr[3] = 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn mm_get_physical_address(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = virtual address -> return physical address
|
||||
ctx.gpr[3] = ctx.gpr[3] & 0x1FFF_FFFF; // Mask to 512MB physical
|
||||
}
|
||||
|
||||
fn mm_query_address_protect(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Return PAGE_READWRITE (0x04)
|
||||
ctx.gpr[3] = 0x04;
|
||||
}
|
||||
|
||||
fn mm_query_statistics(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = stats_ptr — write fake memory statistics
|
||||
let ptr = ctx.gpr[3] as u32;
|
||||
if ptr != 0 {
|
||||
// Total physical = 512MB
|
||||
mem.write_u32(ptr + 0x04, 512 * 1024 * 1024); // TotalPhysicalPages (in bytes)
|
||||
mem.write_u32(ptr + 0x10, 256 * 1024 * 1024); // AvailablePages
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== File I/O =====
|
||||
|
||||
fn nt_create_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle_for(KernelObject::File { path: String::new() });
|
||||
tracing::info!("NtCreateFile: handle={:#x}", handle);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn nt_open_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle_for(KernelObject::File { path: String::new() });
|
||||
tracing::info!("NtOpenFile: handle={:#x}", handle);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn nt_read_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0xC000_0011; // STATUS_END_OF_FILE
|
||||
}
|
||||
|
||||
fn nt_write_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0; // STATUS_SUCCESS (discard data)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn nt_query_directory_file(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0xC000_0034; // STATUS_OBJECT_NAME_NOT_FOUND
|
||||
}
|
||||
|
||||
fn nt_close(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = ctx.gpr[3] as u32;
|
||||
state.objects.remove(&handle);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn nt_create_event(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = handle_ptr, r4 = obj_attrs, r5 = event_type, r6 = initial_state
|
||||
let handle_ptr = ctx.gpr[3] as u32;
|
||||
let manual_reset = ctx.gpr[5] != 0;
|
||||
let signaled = ctx.gpr[6] != 0;
|
||||
let handle = state.alloc_handle_for(KernelObject::Event { manual_reset, signaled });
|
||||
if handle_ptr != 0 {
|
||||
mem.write_u32(handle_ptr, handle);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn nt_create_semaphore(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = handle_ptr, r4 = obj_attrs, r5 = initial_count, r6 = max_count
|
||||
let handle_ptr = ctx.gpr[3] as u32;
|
||||
let count = ctx.gpr[5] as i32;
|
||||
let max = ctx.gpr[6] as i32;
|
||||
let handle = state.alloc_handle_for(KernelObject::Semaphore { count, max });
|
||||
if handle_ptr != 0 {
|
||||
mem.write_u32(handle_ptr, handle);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn nt_create_timer(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle_ptr = ctx.gpr[3] as u32;
|
||||
let handle = state.alloc_handle_for(KernelObject::Timer);
|
||||
if handle_ptr != 0 {
|
||||
mem.write_u32(handle_ptr, handle);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== RTL =====
|
||||
|
||||
fn rtl_initialize_critical_section(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = critical_section_ptr (28 bytes on Xbox 360)
|
||||
let cs_ptr = ctx.gpr[3] as u32;
|
||||
if cs_ptr != 0 {
|
||||
for i in (0..28).step_by(4) {
|
||||
mem.write_u32(cs_ptr + i, 0);
|
||||
}
|
||||
// Set recursion count to -1 (unlocked)
|
||||
mem.write_u32(cs_ptr + 8, 0xFFFF_FFFF_u32);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn rtl_enter_critical_section(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = critical_section_ptr
|
||||
// For single-threaded: increment lock count, always succeed
|
||||
let cs_ptr = ctx.gpr[3] as u32;
|
||||
if cs_ptr != 0 {
|
||||
let lock_count = mem.read_u32(cs_ptr + 4) as i32;
|
||||
mem.write_u32(cs_ptr + 4, (lock_count + 1) as u32);
|
||||
let recursion = mem.read_u32(cs_ptr + 8) as i32;
|
||||
mem.write_u32(cs_ptr + 8, (recursion + 1) as u32);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn rtl_leave_critical_section(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let cs_ptr = ctx.gpr[3] as u32;
|
||||
if cs_ptr != 0 {
|
||||
let lock_count = mem.read_u32(cs_ptr + 4) as i32;
|
||||
mem.write_u32(cs_ptr + 4, (lock_count - 1) as u32);
|
||||
let recursion = mem.read_u32(cs_ptr + 8) as i32;
|
||||
mem.write_u32(cs_ptr + 8, (recursion - 1) as u32);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn rtl_try_enter_critical_section(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Always succeed in single-threaded mode
|
||||
let cs_ptr = ctx.gpr[3] as u32;
|
||||
if cs_ptr != 0 {
|
||||
let lock_count = mem.read_u32(cs_ptr + 4) as i32;
|
||||
mem.write_u32(cs_ptr + 4, (lock_count + 1) as u32);
|
||||
let recursion = mem.read_u32(cs_ptr + 8) as i32;
|
||||
mem.write_u32(cs_ptr + 8, (recursion + 1) as u32);
|
||||
}
|
||||
ctx.gpr[3] = 1; // TRUE
|
||||
}
|
||||
|
||||
fn rtl_init_ansi_string(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let dest_ptr = ctx.gpr[3] as u32;
|
||||
let src_ptr = ctx.gpr[4] as u32;
|
||||
if src_ptr != 0 {
|
||||
let mut len: u16 = 0;
|
||||
let mut addr = src_ptr;
|
||||
while mem.read_u8(addr) != 0 {
|
||||
len += 1;
|
||||
addr += 1;
|
||||
}
|
||||
mem.write_u16(dest_ptr, len);
|
||||
mem.write_u16(dest_ptr + 2, len + 1);
|
||||
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_init_unicode_string(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let dest_ptr = ctx.gpr[3] as u32;
|
||||
let src_ptr = ctx.gpr[4] as u32;
|
||||
if src_ptr != 0 {
|
||||
let mut len: u16 = 0;
|
||||
let mut addr = src_ptr;
|
||||
while mem.read_u16(addr) != 0 {
|
||||
len += 2;
|
||||
addr += 2;
|
||||
}
|
||||
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_capture_context(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = context_ptr — write CPU registers to CONTEXT structure
|
||||
let ptr = ctx.gpr[3] as u32;
|
||||
if ptr != 0 {
|
||||
// Write GPRs at offset 0 (simplified)
|
||||
for i in 0..32 {
|
||||
mem.write_u64(ptr + (i * 8) as u32, ctx.gpr[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rtl_compare_memory_ulong(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = source, r4 = length, r5 = pattern
|
||||
let source = ctx.gpr[3] as u32;
|
||||
let length = ctx.gpr[4] as u32;
|
||||
let pattern = ctx.gpr[5] as u32;
|
||||
let mut matched: u32 = 0;
|
||||
let count = length / 4;
|
||||
for i in 0..count {
|
||||
let val = mem.read_u32(source + i * 4);
|
||||
if val != pattern {
|
||||
break;
|
||||
}
|
||||
matched += 4;
|
||||
}
|
||||
ctx.gpr[3] = matched as u64;
|
||||
}
|
||||
|
||||
fn rtl_fill_memory_ulong(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = destination, r4 = length, r5 = pattern
|
||||
let dest = ctx.gpr[3] as u32;
|
||||
let length = ctx.gpr[4] as u32;
|
||||
let pattern = ctx.gpr[5] as u32;
|
||||
let count = length / 4;
|
||||
for i in 0..count {
|
||||
mem.write_u32(dest + i * 4, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
fn rtl_image_xex_header_field(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = xex_header_ptr, r4 = field_id
|
||||
// Return 0 for all fields
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn rtl_multi_byte_to_unicode_n(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = unicode_str, r4 = max_bytes_out, r5 = bytes_written_ptr
|
||||
// r6 = multi_byte_str, r7 = multi_byte_len
|
||||
let uni_ptr = ctx.gpr[3] as u32;
|
||||
let max_bytes = ctx.gpr[4] as u32;
|
||||
let written_ptr = ctx.gpr[5] as u32;
|
||||
let mb_ptr = ctx.gpr[6] as u32;
|
||||
let mb_len = ctx.gpr[7] as u32;
|
||||
|
||||
let max_chars = max_bytes / 2;
|
||||
let count = std::cmp::min(mb_len, max_chars);
|
||||
for i in 0..count {
|
||||
let byte = mem.read_u8(mb_ptr + i);
|
||||
mem.write_u16(uni_ptr + i * 2, byte as u16);
|
||||
}
|
||||
if written_ptr != 0 {
|
||||
mem.write_u32(written_ptr, count * 2);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn rtl_nt_status_to_dos_error(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Simple mapping for common cases
|
||||
let status = ctx.gpr[3] as u32;
|
||||
ctx.gpr[3] = match status {
|
||||
0 => 0, // ERROR_SUCCESS
|
||||
0xC000_0034 => 2, // ERROR_FILE_NOT_FOUND
|
||||
0xC000_0011 => 38, // ERROR_HANDLE_EOF
|
||||
_ => status as u64, // Pass through
|
||||
};
|
||||
}
|
||||
|
||||
fn rtl_raise_exception(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("RtlRaiseException: record_ptr={:#010x}", ctx.gpr[3]);
|
||||
// Don't halt — just log and return
|
||||
}
|
||||
|
||||
fn rtl_unwind(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("RtlUnwind: target_frame={:#010x}", ctx.gpr[3]);
|
||||
// Stub — in a real implementation this would walk the stack
|
||||
}
|
||||
|
||||
fn stub_sprintf(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
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;
|
||||
}
|
||||
|
||||
fn stub_vsnprintf(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = buffer, r4 = count, r5 = format, r6 = va_list
|
||||
let dest = ctx.gpr[3] as u32;
|
||||
let fmt = ctx.gpr[5] 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;
|
||||
}
|
||||
|
||||
// ===== Video =====
|
||||
|
||||
fn vd_query_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
let mode_ptr = ctx.gpr[3] as u32;
|
||||
if mode_ptr != 0 {
|
||||
mem.write_u32(mode_ptr, 1280);
|
||||
mem.write_u32(mode_ptr + 4, 720);
|
||||
mem.write_u32(mode_ptr + 8, 0); // is_interlaced
|
||||
mem.write_u32(mode_ptr + 12, 1); // is_widescreen
|
||||
mem.write_u32(mode_ptr + 16, 60); // refresh_rate
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn vd_get_system_command_buffer(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = cmd_buffer_ptr_ptr, r4 = cmd_buffer_size_ptr
|
||||
let buf_ptr_ptr = ctx.gpr[3] as u32;
|
||||
let buf_size_ptr = ctx.gpr[4] as u32;
|
||||
|
||||
if state.gpu_command_buffer == 0 {
|
||||
// Allocate a 64KB command buffer
|
||||
if let Some(addr) = state.heap_alloc(0x10000, mem) {
|
||||
state.gpu_command_buffer = addr;
|
||||
}
|
||||
}
|
||||
|
||||
if buf_ptr_ptr != 0 {
|
||||
mem.write_u32(buf_ptr_ptr, state.gpu_command_buffer);
|
||||
}
|
||||
if buf_size_ptr != 0 {
|
||||
mem.write_u32(buf_size_ptr, 0x10000);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn vd_is_hsio_training_succeeded(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 1; // TRUE
|
||||
}
|
||||
|
||||
fn vd_swap(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::info!("VdSwap (frame boundary)");
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Audio =====
|
||||
|
||||
fn xaudio_register_render_driver(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
tracing::info!("XAudioRegisterRenderDriverClient: handle={:#x}", handle);
|
||||
// r3 = callback_ptr, r4 = driver_ptr -> write handle
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xma_create_context(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
tracing::info!("XMACreateContext: handle={:#x}", handle);
|
||||
ctx.gpr[3] = handle as u64;
|
||||
}
|
||||
|
||||
// ===== Xex =====
|
||||
|
||||
fn xex_get_procedure_address(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
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
|
||||
}
|
||||
|
||||
// ===== Exception handling =====
|
||||
|
||||
fn c_specific_handler(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("__C_specific_handler called (exception handling stub)");
|
||||
ctx.gpr[3] = 1; // ExceptionContinueSearch
|
||||
}
|
||||
|
||||
// ===== Helpers =====
|
||||
|
||||
fn read_cstring(mem: &GuestMemory, addr: u32) -> String {
|
||||
let mut s = String::new();
|
||||
let mut a = addr;
|
||||
loop {
|
||||
let c = mem.read_u8(a);
|
||||
if c == 0 { break; }
|
||||
s.push(c as char);
|
||||
a += 1;
|
||||
if s.len() > 512 { break; } // Safety limit
|
||||
}
|
||||
s
|
||||
}
|
||||
6
crates/xenia-kernel/src/lib.rs
Normal file
6
crates/xenia-kernel/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod exports;
|
||||
pub mod objects;
|
||||
pub mod state;
|
||||
pub mod xam;
|
||||
|
||||
pub use state::{KernelState, ModuleId};
|
||||
12
crates/xenia-kernel/src/objects.rs
Normal file
12
crates/xenia-kernel/src/objects.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! Kernel object tracking for HLE.
|
||||
|
||||
/// Kernel object types tracked by handle.
|
||||
#[derive(Debug)]
|
||||
pub enum KernelObject {
|
||||
Event { manual_reset: bool, signaled: bool },
|
||||
Semaphore { count: i32, max: i32 },
|
||||
File { path: String },
|
||||
Thread { id: u32 },
|
||||
Timer,
|
||||
Mutex,
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
253
crates/xenia-kernel/src/xam.rs
Normal file
253
crates/xenia-kernel/src/xam.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
//! HLE kernel export implementations (xam.xex).
|
||||
|
||||
use crate::state::{KernelState, ModuleId};
|
||||
use xenia_cpu::PpcContext;
|
||||
use xenia_memory::{GuestMemory, MemoryAccess};
|
||||
|
||||
pub fn register_exports(state: &mut KernelState) {
|
||||
use ModuleId::Xam;
|
||||
|
||||
// Net
|
||||
state.register_export(Xam, 0x01, "NetDll_WSAStartup", stub_success);
|
||||
state.register_export(Xam, 0x02, "NetDll_WSACleanup", stub_success);
|
||||
|
||||
// Input
|
||||
state.register_export(Xam, 0x0190, "XamInputGetCapabilities", xam_input_not_connected);
|
||||
state.register_export(Xam, 0x0191, "XamInputGetState", xam_input_not_connected);
|
||||
state.register_export(Xam, 0x0192, "XamInputSetState", xam_input_not_connected);
|
||||
state.register_export(Xam, 0x0198, "XamInputGetKeystrokeEx", xam_input_not_connected);
|
||||
|
||||
// Inactivity
|
||||
state.register_export(Xam, 0x01A0, "XamEnableInactivityProcessing", stub_success);
|
||||
state.register_export(Xam, 0x01A1, "XamResetInactivity", stub_success);
|
||||
|
||||
// Loader
|
||||
state.register_export(Xam, 0x01A4, "XamLoaderLaunchTitle", xam_loader_launch_title);
|
||||
state.register_export(Xam, 0x01A9, "XamLoaderTerminateTitle", xam_loader_terminate_title);
|
||||
|
||||
// Task
|
||||
state.register_export(Xam, 0x01AF, "XamTaskSchedule", xam_task_schedule);
|
||||
state.register_export(Xam, 0x01B1, "XamTaskCloseHandle", stub_success);
|
||||
state.register_export(Xam, 0x01B3, "XamTaskShouldExit", stub_return_zero);
|
||||
|
||||
// Alloc
|
||||
state.register_export(Xam, 0x01EA, "XamAlloc", xam_alloc);
|
||||
state.register_export(Xam, 0x01EC, "XamFree", stub_success);
|
||||
|
||||
// Msg
|
||||
state.register_export(Xam, 0x01F4, "XMsgInProcessCall", stub_success);
|
||||
state.register_export(Xam, 0x01F7, "XMsgStartIORequest", stub_success);
|
||||
state.register_export(Xam, 0x01FC, "XMsgStartIORequestEx", stub_success);
|
||||
|
||||
// User
|
||||
state.register_export(Xam, 0x020A, "XamUserGetXUID", xam_user_get_xuid);
|
||||
state.register_export(Xam, 0x020E, "XamUserGetName", xam_user_get_name);
|
||||
state.register_export(Xam, 0x0210, "XamUserGetSigninState", stub_return_zero);
|
||||
state.register_export(Xam, 0x0219, "XamUserReadProfileSettings", xam_user_read_profile_settings);
|
||||
state.register_export(Xam, 0x021A, "XamUserWriteProfileSettings", stub_success);
|
||||
|
||||
// Enum
|
||||
state.register_export(Xam, 0x0250, "XamEnumerate", stub_error_no_more_files);
|
||||
|
||||
// Content
|
||||
state.register_export(Xam, 0x0258, "XamContentCreate", stub_success);
|
||||
state.register_export(Xam, 0x025A, "XamContentClose", stub_success);
|
||||
state.register_export(Xam, 0x025B, "XamContentDelete", stub_success);
|
||||
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
|
||||
state.register_export(Xam, 0x025E, "XamContentGetDeviceData", stub_success);
|
||||
state.register_export(Xam, 0x025F, "XamContentGetDeviceName", stub_success);
|
||||
state.register_export(Xam, 0x0260, "XamContentSetThumbnail", stub_success);
|
||||
state.register_export(Xam, 0x0262, "XamContentGetCreator", stub_success);
|
||||
state.register_export(Xam, 0x0265, "XamContentGetDeviceState", stub_success);
|
||||
|
||||
// System
|
||||
state.register_export(Xam, 0x0280, "XamGetExecutionId", xam_get_execution_id);
|
||||
state.register_export(Xam, 0x0282, "XamGetSystemVersion", xam_get_system_version);
|
||||
|
||||
// Notify
|
||||
state.register_export(Xam, 0x028A, "XamNotifyCreateListener", xam_notify_create_listener);
|
||||
state.register_export(Xam, 0x028B, "XNotifyGetNext", xnotify_get_next);
|
||||
state.register_export(Xam, 0x028C, "XNotifyPositionUI", stub_success);
|
||||
|
||||
// Achievements/Stats
|
||||
state.register_export(Xam, 0x02EE, "XamUserCreateAchievementEnumerator", stub_success);
|
||||
state.register_export(Xam, 0x02F7, "XamUserCreateStatsEnumerator", stub_success);
|
||||
|
||||
// UI
|
||||
state.register_export(Xam, 0x02BC, "XamShowSigninUI", stub_success);
|
||||
state.register_export(Xam, 0x02C1, "XamShowKeyboardUI", stub_success);
|
||||
state.register_export(Xam, 0x02CB, "XamShowDeviceSelectorUI", stub_success);
|
||||
state.register_export(Xam, 0x02D5, "XamShowGamerCardUIForXUID", stub_success);
|
||||
state.register_export(Xam, 0x02D9, "XamShowDirtyDiscErrorUI", stub_success);
|
||||
state.register_export(Xam, 0x02DC, "XamShowMessageBoxUIEx", stub_success);
|
||||
|
||||
// Session
|
||||
state.register_export(Xam, 0x0316, "XamSessionCreateHandle", xam_session_create_handle);
|
||||
state.register_export(Xam, 0x0317, "XamSessionRefObjByHandle", stub_success);
|
||||
|
||||
// Locale
|
||||
state.register_export(Xam, 0x03CB, "XGetAVPack", xget_avpack);
|
||||
state.register_export(Xam, 0x03CC, "XGetGameRegion", xget_game_region);
|
||||
state.register_export(Xam, 0x03CD, "XGetLanguage", xget_language);
|
||||
state.register_export(Xam, 0x03D1, "XGetVideoMode", xget_video_mode);
|
||||
}
|
||||
|
||||
// ===== Generic stubs =====
|
||||
|
||||
fn stub_success(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn stub_return_zero(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn stub_error_no_more_files(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0x12; // ERROR_NO_MORE_FILES
|
||||
}
|
||||
|
||||
// ===== Input =====
|
||||
|
||||
fn xam_input_not_connected(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0x48F; // ERROR_DEVICE_NOT_CONNECTED
|
||||
}
|
||||
|
||||
// ===== Loader =====
|
||||
|
||||
fn xam_loader_launch_title(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("XamLoaderLaunchTitle called");
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xam_loader_terminate_title(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
tracing::warn!("XamLoaderTerminateTitle called");
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Task =====
|
||||
|
||||
fn xam_task_schedule(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
tracing::info!("XamTaskSchedule: handle={:#x}", handle);
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Alloc =====
|
||||
|
||||
fn xam_alloc(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = flags, r4 = size, r5 = out_ptr_ptr
|
||||
let size = ctx.gpr[4] as u32;
|
||||
let out_ptr = ctx.gpr[5] as u32;
|
||||
|
||||
match state.heap_alloc(size, mem) {
|
||||
Some(addr) => {
|
||||
if out_ptr != 0 {
|
||||
mem.write_u32(out_ptr, addr);
|
||||
}
|
||||
ctx.gpr[3] = 0; // SUCCESS
|
||||
}
|
||||
None => {
|
||||
ctx.gpr[3] = 0x8007_000E; // E_OUTOFMEMORY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== User =====
|
||||
|
||||
fn xam_user_get_xuid(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = user_index, r4 = xuid_ptr
|
||||
let xuid_ptr = ctx.gpr[4] as u32;
|
||||
if xuid_ptr != 0 {
|
||||
mem.write_u64(xuid_ptr, 0); // No XUID
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xam_user_get_name(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = user_index, r4 = buffer, r5 = buffer_size
|
||||
let buffer = ctx.gpr[4] as u32;
|
||||
if buffer != 0 {
|
||||
mem.write_u8(buffer, 0); // Empty string
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xam_user_read_profile_settings(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// Return error — no profile
|
||||
ctx.gpr[3] = 0x0000_048B; // ERROR_NOT_FOUND
|
||||
}
|
||||
|
||||
// ===== System =====
|
||||
|
||||
fn xam_get_execution_id(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = execution_id_ptr_ptr — write pointer to execution info
|
||||
let ptr_ptr = ctx.gpr[3] as u32;
|
||||
if ptr_ptr != 0 {
|
||||
// Allocate and fill a fake XEX_EXECUTION_ID structure
|
||||
if let Some(exec_id_addr) = state.heap_alloc(0x18, mem) {
|
||||
mem.write_u32(exec_id_addr, 0x535107D4); // title_id (Project Sylpheed)
|
||||
mem.write_u32(exec_id_addr + 4, 0x2D2E2EEB); // media_id
|
||||
mem.write_u16(exec_id_addr + 8, 0); // version
|
||||
mem.write_u16(exec_id_addr + 10, 0); // base_version
|
||||
mem.write_u16(exec_id_addr + 12, 1); // disc_number
|
||||
mem.write_u16(exec_id_addr + 14, 1); // disc_count
|
||||
mem.write_u32(ptr_ptr, exec_id_addr);
|
||||
}
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
fn xam_get_system_version(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0x2000_0000; // System version
|
||||
}
|
||||
|
||||
// ===== Notify =====
|
||||
|
||||
fn xam_notify_create_listener(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
let handle = state.alloc_handle();
|
||||
ctx.gpr[3] = handle as u64;
|
||||
}
|
||||
|
||||
fn xnotify_get_next(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = handle, r4 = id_ptr, r5 = param_ptr
|
||||
ctx.gpr[3] = 0; // FALSE (no notifications)
|
||||
}
|
||||
|
||||
// ===== Session =====
|
||||
|
||||
fn xam_session_create_handle(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
|
||||
// r3 = handle_ptr
|
||||
let handle_ptr = ctx.gpr[3] as u32;
|
||||
let handle = state.alloc_handle();
|
||||
if handle_ptr != 0 {
|
||||
mem.write_u32(handle_ptr, handle);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
// ===== Locale =====
|
||||
|
||||
fn xget_avpack(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0x16; // HDMI
|
||||
}
|
||||
|
||||
fn xget_game_region(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 0xFF; // All regions
|
||||
}
|
||||
|
||||
fn xget_language(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
ctx.gpr[3] = 1; // English
|
||||
}
|
||||
|
||||
fn xget_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
|
||||
// r3 = video_mode_ptr
|
||||
let ptr = ctx.gpr[3] as u32;
|
||||
if ptr != 0 {
|
||||
mem.write_u32(ptr, 1280); // width
|
||||
mem.write_u32(ptr + 4, 720); // height
|
||||
mem.write_u32(ptr + 8, 0); // is_interlaced
|
||||
mem.write_u32(ptr + 12, 1); // is_widescreen
|
||||
mem.write_u32(ptr + 16, 60); // refresh_rate
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user