Initial xenia-rs

This commit is contained in:
MechaCat02
2026-04-12 18:25:46 +02:00
parent 1da37db584
commit 06a23212fb
50 changed files with 6759 additions and 0 deletions

View 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 }

View File

@@ -0,0 +1,142 @@
//! HLE kernel export implementations.
//! Each export mirrors a function from xboxkrnl_table.inc.
use crate::state::{KernelState, ModuleId};
use xenia_cpu::PpcContext;
use xenia_memory::GuestMemory;
pub fn register_exports(state: &mut KernelState) {
use ModuleId::Xboxkrnl;
// Memory
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);
// Threading
state.register_export(Xboxkrnl, 0x0C, "ExCreateThread", ex_create_thread);
state.register_export(Xboxkrnl, 0x5F, "KeDelayExecutionThread", ke_delay_execution_thread);
// Sync
state.register_export(Xboxkrnl, 0xC0, "NtCreateEvent", nt_create_event);
state.register_export(Xboxkrnl, 0x63, "KeSetEvent", ke_set_event);
state.register_export(Xboxkrnl, 0x6B, "KeWaitForSingleObject", ke_wait_for_single_object);
state.register_export(Xboxkrnl, 0x53, "NtClose", nt_close);
// Module
state.register_export(Xboxkrnl, 0x195, "XexGetModuleHandle", xex_get_module_handle);
// RTL
state.register_export(Xboxkrnl, 0x11A, "RtlInitAnsiString", rtl_init_ansi_string);
// Video
state.register_export(Xboxkrnl, 0x142, "VdGetCurrentDisplayGamma", vd_get_current_display_gamma);
state.register_export(Xboxkrnl, 0x14B, "VdQueryVideoMode", vd_query_video_mode);
// Debug
state.register_export(Xboxkrnl, 0x166, "DbgPrint", dbg_print);
}
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
}
fn nt_free_virtual_memory(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
fn nt_query_virtual_memory(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
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 nt_create_event(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut KernelState) {
let _handle = state.alloc_handle();
ctx.gpr[3] = 0;
}
fn ke_set_event(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
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
}
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
}
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 {
len += 1;
addr += 1;
}
// Write ANSI_STRING struct: {Length, MaxLength, Buffer}
mem.write_u16(dest_ptr, len);
mem.write_u16(dest_ptr + 2, len + 1);
mem.write_u32(dest_ptr + 4, src_ptr);
}
}
fn vd_get_current_display_gamma(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
fn vd_query_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
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
mem.write_u32(mode_ptr + 12, 0); // is_widescreen
mem.write_u32(mode_ptr + 16, 60); // refresh_rate
}
ctx.gpr[3] = 0;
}
fn dbg_print(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
use xenia_memory::MemoryAccess;
let str_ptr = ctx.gpr[3] as u32;
if str_ptr != 0 {
let mut s = String::new();
let mut addr = str_ptr;
loop {
let c = mem.read_u8(addr);
if c == 0 { break; }
s.push(c as char);
addr += 1;
}
tracing::info!("DbgPrint: {}", s);
}
ctx.gpr[3] = 0;
}

View File

@@ -0,0 +1,4 @@
pub mod exports;
pub mod state;
pub use state::KernelState;

View File

@@ -0,0 +1,80 @@
use std::collections::HashMap;
use xenia_cpu::PpcContext;
use xenia_memory::GuestMemory;
/// 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,
}
impl KernelState {
pub fn new() -> Self {
let mut state = Self {
exports: HashMap::new(),
next_handle: 0x1000,
};
crate::exports::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 alloc_handle(&mut self) -> u32 {
let h = self.next_handle;
self.next_handle += 4;
h
}
}
impl Default for KernelState {
fn default() -> Self {
Self::new()
}
}