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:
139
crates/xenia-kernel/src/path.rs
Normal file
139
crates/xenia-kernel/src/path.rs
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user