Initial xenia-rs
This commit is contained in:
25
xenia-rs/crates/xenia-app/Cargo.toml
Normal file
25
xenia-rs/crates/xenia-app/Cargo.toml
Normal 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"] }
|
||||
244
xenia-rs/crates/xenia-app/src/main.rs
Normal file
244
xenia-rs/crates/xenia-app/src/main.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user