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>
140 lines
4.1 KiB
Rust
140 lines
4.1 KiB
Rust
//! 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"
|
|
);
|
|
}
|
|
}
|