Initial xenia-rs

This commit is contained in:
MechaCat02
2026-04-12 18:25:46 +02:00
parent 1da37db584
commit 06a23212fb
50 changed files with 6759 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
[package]
name = "xenia-app"
version.workspace = true
edition.workspace = true
license.workspace = true
[[bin]]
name = "xenia-rs"
path = "src/main.rs"
[dependencies]
xenia-types = { workspace = true }
xenia-memory = { workspace = true }
xenia-cpu = { workspace = true }
xenia-xex = { workspace = true }
xenia-vfs = { workspace = true }
xenia-kernel = { workspace = true }
xenia-gpu = { workspace = true }
xenia-apu = { workspace = true }
xenia-hid = { workspace = true }
xenia-debugger = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
anyhow = { workspace = true }
clap = { version = "4", features = ["derive"] }

View File

@@ -0,0 +1,244 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(name = "xenia-rs")]
#[command(about = "Xbox 360 emulator for reverse engineering and preservation")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Disassemble a XEX file from its entry point
Disasm {
/// Path to XEX file
path: String,
/// Number of instructions to disassemble
#[arg(short = 'n', default_value = "64")]
count: usize,
},
/// Load and execute a XEX file with tracing
Exec {
/// Path to XEX file
path: String,
/// Maximum instructions to execute before stopping
#[arg(short = 'n', default_value = "1000")]
max_instructions: u64,
},
/// Browse XISO disc image contents
Browse {
/// Path to XISO file
path: String,
},
/// Display XEX header information
Info {
/// Path to XEX file
path: String,
},
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive("info".parse()?))
.init();
let cli = Cli::parse();
match cli.command {
Commands::Disasm { path, count } => cmd_disasm(&path, count),
Commands::Exec { path, max_instructions } => cmd_exec(&path, max_instructions),
Commands::Browse { path } => cmd_browse(&path),
Commands::Info { path } => cmd_info(&path),
}
}
fn cmd_info(path: &str) -> Result<()> {
let data = std::fs::read(path)?;
let header = xenia_xex::loader::parse_xex2_header(&data)?;
println!("=== XEX2 Header ===");
println!("Magic: {:#010x}", header.magic);
println!("Module Flags: {:#010x}", header.module_flags);
println!("Header Size: {:#x}", header.header_size);
println!("Headers: {}", header.header_count);
if let Some(entry) = xenia_xex::loader::get_entry_point(&header) {
println!("Entry Point: {:#010x}", entry);
}
if let Some(base) = xenia_xex::loader::get_image_base(&header) {
println!("Image Base: {:#010x}", base);
}
println!("\n=== Optional Headers ===");
for h in &header.optional_headers {
println!(" Key: {:#010x} Value: {:#010x}", h.key, h.value);
}
if let Some(ref sec) = header.security_info {
println!("\n=== Security Info ===");
println!("Image Size: {:#x}", sec.image_size);
println!("Load Address: {:#010x}", sec.load_address);
println!("Image Flags: {:#010x}", sec.image_flags);
println!("Page Descs: {}", sec.page_descriptors.len());
}
Ok(())
}
fn cmd_disasm(path: &str, count: usize) -> Result<()> {
let data = std::fs::read(path)?;
let header = xenia_xex::loader::parse_xex2_header(&data)?;
let entry = xenia_xex::loader::get_entry_point(&header)
.ok_or_else(|| anyhow::anyhow!("No entry point found in XEX2 header"))?;
let base = xenia_xex::loader::get_image_base(&header)
.ok_or_else(|| anyhow::anyhow!("No image base found in XEX2 header"))?;
println!("Entry point: {:#010x}, Image base: {:#010x}", entry, base);
println!("Disassembly from entry point ({} instructions):\n", count);
// For now, disassemble from the raw file data at the entry offset
let entry_offset = (entry - base) as usize + header.header_size as usize;
if entry_offset + count * 4 <= data.len() {
let block = xenia_cpu::disasm::disassemble_block(&data[entry_offset..], entry, count);
for (addr, text) in block {
println!(" {:#010x}: {}", addr, text);
}
} else {
println!(" (entry point offset {:#x} is outside file bounds)", entry_offset);
}
Ok(())
}
fn cmd_exec(path: &str, max_instructions: u64) -> Result<()> {
let data = std::fs::read(path)?;
let header = xenia_xex::loader::parse_xex2_header(&data)?;
let entry = xenia_xex::loader::get_entry_point(&header)
.ok_or_else(|| anyhow::anyhow!("No entry point found"))?;
let base = xenia_xex::loader::get_image_base(&header)
.ok_or_else(|| anyhow::anyhow!("No image base found"))?;
println!("Loading XEX: entry={:#010x} base={:#010x}", entry, base);
// Allocate guest memory
let mut mem = xenia_memory::GuestMemory::new()
.map_err(|e| anyhow::anyhow!("Failed to allocate guest memory: {}", e))?;
// Map the XEX image into guest memory
let image_data = &data[header.header_size as usize..];
let alloc_size = ((image_data.len() + 4095) & !4095) as u32;
mem.alloc(
base,
alloc_size,
xenia_memory::page_table::MemoryProtect::READ | xenia_memory::page_table::MemoryProtect::WRITE,
).map_err(|e| anyhow::anyhow!("Failed to allocate guest memory region: {}", e))?;
mem.write_bulk(base, image_data);
// Allocate stack (1MB at 0x70000000)
let stack_base = 0x7000_0000u32;
let stack_size = 0x10_0000u32;
mem.alloc(
stack_base,
stack_size,
xenia_memory::page_table::MemoryProtect::READ | xenia_memory::page_table::MemoryProtect::WRITE,
).map_err(|e| anyhow::anyhow!("Failed to allocate stack: {}", e))?;
// Set up CPU context
let mut ctx = xenia_cpu::PpcContext::new();
ctx.pc = entry;
ctx.gpr[1] = (stack_base + stack_size - 0x80) as u64; // Stack pointer (with red zone)
ctx.gpr[13] = 0; // Small data area (TLS)
// Set up kernel
let mut _kernel = xenia_kernel::KernelState::new();
// Set up debugger
let mut debugger = xenia_debugger::Debugger::new();
debugger.paused = false;
debugger.step_mode = xenia_debugger::StepMode::Run;
debugger.trace_enabled = true;
println!("Starting execution (max {} instructions)...\n", max_instructions);
use xenia_cpu::interpreter::{step, StepResult};
let mut instruction_count: u64 = 0;
loop {
if instruction_count >= max_instructions {
println!("\nReached max instruction count ({})", max_instructions);
break;
}
// Pre-step debugger
debugger.pre_step(&ctx, &mem);
let result = step(&mut ctx, &mut mem);
instruction_count += 1;
// Post-step debugger
debugger.post_step(&ctx, &mem);
match result {
StepResult::Continue => {}
StepResult::SystemCall => {
println!("[{:>8}] SYSCALL at {:#010x}", instruction_count, ctx.pc.wrapping_sub(4));
}
StepResult::Unimplemented(op) => {
println!("[{:>8}] UNIMPL: {:?} at {:#010x}", instruction_count, op, ctx.pc.wrapping_sub(4));
}
StepResult::Trap => {
println!("[{:>8}] TRAP at {:#010x}", instruction_count, ctx.pc.wrapping_sub(4));
}
StepResult::Halted => {
println!("[{:>8}] HALTED", instruction_count);
break;
}
}
if debugger.should_break() {
println!("[{:>8}] BREAK at {:#010x}", instruction_count, ctx.pc);
break;
}
}
println!("\n=== Final State ===");
println!("PC: {:#010x}", ctx.pc);
println!("LR: {:#010x}", ctx.lr as u32);
println!("CTR: {:#010x}", ctx.ctr as u32);
println!("CR: {:#010x}", ctx.cr());
println!("XER: CA={} OV={} SO={}", ctx.xer_ca, ctx.xer_ov, ctx.xer_so);
for i in 0..32 {
if ctx.gpr[i] != 0 {
println!("r{:<2}: {:#018x}", i, ctx.gpr[i]);
}
}
println!("\nExecuted {} instructions", instruction_count);
println!("Trace log: {} entries", debugger.trace_log.len());
Ok(())
}
fn cmd_browse(path: &str) -> Result<()> {
use xenia_vfs::VfsDevice;
let disc = xenia_vfs::disc_image::DiscImageDevice::open("disc", std::path::Path::new(path))
.map_err(|e| anyhow::anyhow!("Failed to open disc image: {}", e))?;
println!("=== XISO Contents: {} ===", path);
match disc.list_root() {
Ok(entries) => {
for entry in entries {
let kind = if entry.is_directory { "DIR " } else { "FILE" };
println!(" {} {:>10} {}", kind, entry.size, entry.name);
}
}
Err(e) => println!(" Error listing contents: {}", e),
}
Ok(())
}