Initial xenia-rs
This commit is contained in:
13
xenia-rs/crates/xenia-kernel/Cargo.toml
Normal file
13
xenia-rs/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 }
|
||||
142
xenia-rs/crates/xenia-kernel/src/exports.rs
Normal file
142
xenia-rs/crates/xenia-kernel/src/exports.rs
Normal 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;
|
||||
}
|
||||
4
xenia-rs/crates/xenia-kernel/src/lib.rs
Normal file
4
xenia-rs/crates/xenia-kernel/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod exports;
|
||||
pub mod state;
|
||||
|
||||
pub use state::KernelState;
|
||||
80
xenia-rs/crates/xenia-kernel/src/state.rs
Normal file
80
xenia-rs/crates/xenia-kernel/src/state.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user