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

@@ -0,0 +1,139 @@
//! Path normalization for kernel file I/O.
//!
//! Guests pass file paths inside an `OBJECT_ATTRIBUTES` struct that points at
//! an `ANSI_STRING` descriptor. Those paths come in several Xbox-flavored
//! forms — NT device paths (`\Device\Cdrom0\...`), drive letters (`D:\...`,
//! `d:\...`), or symbolic link prefixes (`game:\...`). We strip whichever
//! prefix applies and return a plain slash-separated path relative to the
//! mounted VFS root, so `VfsDevice::read_file` can look it up directly.
use xenia_memory::{GuestMemory, MemoryAccess};
/// Xbox `ANSI_STRING`:
/// u16 Length
/// u16 MaximumLength
/// u32 Buffer (guest pointer)
fn read_ansi_string(mem: &GuestMemory, ptr: u32) -> Option<String> {
if ptr == 0 {
return None;
}
let length = mem.read_u16(ptr) as u32;
let buffer = mem.read_u32(ptr + 4);
if buffer == 0 || length == 0 {
return Some(String::new());
}
let mut out = String::with_capacity(length as usize);
for i in 0..length {
let c = mem.read_u8(buffer + i);
if c == 0 {
break;
}
out.push(c as char);
}
Some(out)
}
/// Xbox `OBJECT_ATTRIBUTES`:
/// u32 RootDirectory (handle)
/// u32 Name (pointer to ANSI_STRING)
/// u32 Attributes
fn read_object_attributes_name(mem: &GuestMemory, obj_attrs_ptr: u32) -> Option<String> {
if obj_attrs_ptr == 0 {
return None;
}
let name_ptr = mem.read_u32(obj_attrs_ptr + 4);
read_ansi_string(mem, name_ptr)
}
/// Known Xbox device prefixes that need to be stripped before looking a path
/// up in the VFS. The list mirrors the symbolic links xenia-canary sets up
/// at boot (see `xboxkrnl_io.cc`). Case-insensitive matching.
const DEVICE_PREFIXES: &[&str] = &[
"\\Device\\Cdrom0\\",
"\\Device\\Harddisk0\\Partition1\\",
"\\Device\\Harddisk0\\Partition0\\",
"\\Device\\Harddisk0\\",
"\\Device\\Mu0\\",
"\\Device\\Mu1\\",
"\\Device\\Mass0\\",
"\\Device\\Mass1\\",
"\\Device\\Mass2\\",
"\\SystemRoot\\",
"\\??\\",
"game:\\",
"d:\\",
"D:\\",
];
/// Strip any Xbox device prefix and normalize backslashes to forward slashes.
/// Returns the path relative to the VFS root.
pub fn normalize_path(raw: &str) -> String {
let mut s = raw.trim().to_string();
// Case-insensitive prefix strip.
let lowered = s.to_ascii_lowercase();
for prefix in DEVICE_PREFIXES {
let pl = prefix.to_ascii_lowercase();
if lowered.starts_with(&pl) {
s = s[pl.len()..].to_string();
break;
}
}
// Drop any leading slash/backslash that survived prefix stripping.
while s.starts_with('\\') || s.starts_with('/') {
s.remove(0);
}
// Canonical form: forward slashes.
s.replace('\\', "/")
}
/// Convenience: read the OBJECT_ATTRIBUTES struct at `obj_attrs_ptr` and
/// return a normalized VFS path. Returns `None` if the struct pointer or its
/// inner name pointer is null.
pub fn object_attributes_to_vfs_path(mem: &GuestMemory, obj_attrs_ptr: u32) -> Option<String> {
let raw = read_object_attributes_name(mem, obj_attrs_ptr)?;
if raw.is_empty() {
return None;
}
Some(normalize_path(&raw))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_device_cdrom() {
assert_eq!(normalize_path("\\Device\\Cdrom0\\default.xex"), "default.xex");
}
#[test]
fn strips_drive_letter_lowercase() {
assert_eq!(normalize_path("d:\\media\\shared\\foo.pkg"), "media/shared/foo.pkg");
}
#[test]
fn strips_drive_letter_uppercase() {
assert_eq!(normalize_path("D:\\default.xex"), "default.xex");
}
#[test]
fn strips_game_prefix() {
assert_eq!(normalize_path("game:\\data\\whatever.bin"), "data/whatever.bin");
}
#[test]
fn preserves_relative_path() {
assert_eq!(normalize_path("scripts/init.lua"), "scripts/init.lua");
}
#[test]
fn handles_partition1() {
assert_eq!(
normalize_path("\\Device\\Harddisk0\\Partition1\\content\\abc.sav"),
"content/abc.sav"
);
}
}