xenia-kernel: HLE expansion, scheduler integration, audit + UI bridge

Major HLE buildout in exports.rs: KeInitializeSemaphore now seeds
count/limit, XexGet{Module,Procedure}Address use distinct
HMODULE_XBOXKRNL/HMODULE_XAM pseudo-handles with a reverse
(ModuleId,ordinal)→thunk_addr map, plus sweeping additions across
sync primitives, file I/O, semaphores, events, threads, and
allocator paths needed to advance Sylpheed past VdSwap=2.

New modules:
  - thread.rs   — ThreadRef + per-thread suspension/wake plumbing
  - interrupts.rs — IRQ delivery, pending-IRQ slots, IPI helpers
  - path.rs     — guest path normalization (D:\\, game:\\, etc.)
  - audit.rs    — --trace-handles harness backing the handle audit
  - ui_bridge.rs — kernel-side endpoint of the xenia-ui bridge
                   (input snapshots, framebuffer publish handles)

state.rs grows to own the HW-slot scheduler state, the new audit /
UI bridge handles, and the per-handle reverse maps. xam.rs and
objects.rs follow suit for the HLE additions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-01 16:29:00 +02:00
parent f1fadb5398
commit 5f0d6487ea
11 changed files with 6369 additions and 270 deletions

View File

@@ -12,10 +12,10 @@ pub fn register_exports(state: &mut KernelState) {
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);
state.register_export(Xam, 0x0190, "XamInputGetCapabilities", xam_input_get_capabilities);
state.register_export(Xam, 0x0191, "XamInputGetState", xam_input_get_state);
state.register_export(Xam, 0x0192, "XamInputSetState", xam_input_set_state);
state.register_export(Xam, 0x0198, "XamInputGetKeystrokeEx", xam_input_get_keystroke);
// Inactivity
state.register_export(Xam, 0x01A0, "XamEnableInactivityProcessing", stub_success);
@@ -94,39 +94,114 @@ pub fn register_exports(state: &mut KernelState) {
// ===== Generic stubs =====
fn stub_success(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn stub_success(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
fn stub_return_zero(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn stub_return_zero(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0;
}
fn stub_error_no_more_files(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn stub_error_no_more_files(ctx: &mut PpcContext, _mem: &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
/// Helper: pack a `GamepadState` into a 12-byte key used to detect input
/// changes. Cheap to compare across frames.
fn gamepad_key(state: &xenia_hid::GamepadState) -> u128 {
let mut bytes = [0u8; 16];
bytes[0..2].copy_from_slice(&state.buttons.to_be_bytes());
bytes[2] = state.left_trigger;
bytes[3] = state.right_trigger;
bytes[4..6].copy_from_slice(&state.left_stick_x.to_be_bytes());
bytes[6..8].copy_from_slice(&state.left_stick_y.to_be_bytes());
bytes[8..10].copy_from_slice(&state.right_stick_x.to_be_bytes());
bytes[10..12].copy_from_slice(&state.right_stick_y.to_be_bytes());
u128::from_be_bytes(bytes)
}
fn xam_input_get_capabilities(
ctx: &mut PpcContext,
mem: &GuestMemory,
state: &mut KernelState,
) {
// r3 = user_index, r4 = flags, r5 = out X_INPUT_CAPABILITIES*
let user = ctx.gpr[3] as u32;
let out_ptr = ctx.gpr[5] as u32;
let connected = state.ui.as_ref().is_some_and(|ui| ui.is_connected(user));
if !connected {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
}
xenia_hid::write_input_capabilities(mem, out_ptr);
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
}
fn xam_input_get_state(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
// r3 = user_index, r4 = flags, r5 = out X_INPUT_STATE*
let user = ctx.gpr[3] as u32;
let out_ptr = ctx.gpr[5] as u32;
let Some(ui) = state.ui.as_ref() else {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
};
if !ui.is_connected(user) {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
}
let gamepad = ui.snapshot_gamepad();
let key = gamepad_key(&gamepad);
if key != state.last_input_bytes {
state.input_packet_number = state.input_packet_number.wrapping_add(1);
state.last_input_bytes = key;
}
xenia_hid::write_input_state(mem, out_ptr, state.input_packet_number, &gamepad);
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
}
fn xam_input_set_state(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
// r3 = user_index, r4 = flags, r5 = X_INPUT_VIBRATION*
// Rumble is out of scope for Phase 1; we accept the call and return
// success so games don't retry in a tight loop, but we never actually
// shake anything.
let user = ctx.gpr[3] as u32;
let connected = state.ui.as_ref().is_some_and(|ui| ui.is_connected(user));
if !connected {
ctx.gpr[3] = xenia_hid::errors::DEVICE_NOT_CONNECTED as u64;
return;
}
ctx.gpr[3] = xenia_hid::errors::SUCCESS as u64;
}
fn xam_input_get_keystroke(
ctx: &mut PpcContext,
_mem: &GuestMemory,
_state: &mut KernelState,
) {
// No keyboard input in Phase 1 — always "queue empty". Games that only
// use the gamepad ignore this return code; those that drive text entry
// through the keystroke queue simply get a permanently empty queue, which
// manifests as no virtual-keyboard input — acceptable for minimal UI.
ctx.gpr[3] = xenia_hid::errors::EMPTY as u64;
}
// ===== Loader =====
fn xam_loader_launch_title(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xam_loader_launch_title(ctx: &mut PpcContext, _mem: &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) {
fn xam_loader_terminate_title(ctx: &mut PpcContext, _mem: &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) {
fn xam_task_schedule(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
let handle = state.alloc_handle();
tracing::info!("XamTaskSchedule: handle={:#x}", handle);
ctx.gpr[3] = 0;
@@ -134,7 +209,7 @@ fn xam_task_schedule(ctx: &mut PpcContext, _mem: &mut GuestMemory, state: &mut K
// ===== Alloc =====
fn xam_alloc(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelState) {
fn xam_alloc(ctx: &mut PpcContext, mem: &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;
@@ -154,7 +229,7 @@ fn xam_alloc(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut KernelStat
// ===== User =====
fn xam_user_get_xuid(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
fn xam_user_get_xuid(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
// r3 = user_index, r4 = xuid_ptr
let xuid_ptr = ctx.gpr[4] as u32;
if xuid_ptr != 0 {
@@ -163,7 +238,7 @@ fn xam_user_get_xuid(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut K
ctx.gpr[3] = 0;
}
fn xam_user_get_name(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
fn xam_user_get_name(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
// r3 = user_index, r4 = buffer, r5 = buffer_size
let buffer = ctx.gpr[4] as u32;
if buffer != 0 {
@@ -172,14 +247,14 @@ fn xam_user_get_name(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut K
ctx.gpr[3] = 0;
}
fn xam_user_read_profile_settings(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xam_user_read_profile_settings(ctx: &mut PpcContext, _mem: &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) {
fn xam_get_execution_id(ctx: &mut PpcContext, mem: &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 {
@@ -197,25 +272,25 @@ fn xam_get_execution_id(ctx: &mut PpcContext, mem: &mut GuestMemory, state: &mut
ctx.gpr[3] = 0;
}
fn xam_get_system_version(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xam_get_system_version(ctx: &mut PpcContext, _mem: &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) {
fn xam_notify_create_listener(ctx: &mut PpcContext, _mem: &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) {
fn xnotify_get_next(ctx: &mut PpcContext, _mem: &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) {
fn xam_session_create_handle(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
// r3 = handle_ptr
let handle_ptr = ctx.gpr[3] as u32;
let handle = state.alloc_handle();
@@ -227,19 +302,19 @@ fn xam_session_create_handle(ctx: &mut PpcContext, mem: &mut GuestMemory, state:
// ===== Locale =====
fn xget_avpack(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xget_avpack(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0x16; // HDMI
}
fn xget_game_region(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xget_game_region(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0xFF; // All regions
}
fn xget_language(ctx: &mut PpcContext, _mem: &mut GuestMemory, _state: &mut KernelState) {
fn xget_language(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 1; // English
}
fn xget_video_mode(ctx: &mut PpcContext, mem: &mut GuestMemory, _state: &mut KernelState) {
fn xget_video_mode(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
// r3 = video_mode_ptr
let ptr = ctx.gpr[3] as u32;
if ptr != 0 {