Implement full decrypt + decompress pipeline for XEX2 PE extraction: - decompress.rs: None, Basic (zero-fill), and Normal (LZX) decompression - extract.rs: orchestrates decryption then decompression - Wire up CLI extract command to write PE files - LZX decompression via lzxd crate with per-frame chunk processing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
91 lines
2.5 KiB
Rust
91 lines
2.5 KiB
Rust
use clap::{Parser, Subcommand};
|
|
use std::path::PathBuf;
|
|
use std::process;
|
|
|
|
/// A tool for extracting and inspecting Xbox 360 XEX2 executable files.
|
|
#[derive(Parser)]
|
|
#[command(name = "xex2tractor", version, about)]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Command,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Command {
|
|
/// Display XEX2 file information (headers, security info, etc.)
|
|
Inspect {
|
|
/// Path to the XEX2 file
|
|
file: PathBuf,
|
|
},
|
|
/// Extract the PE image from a XEX2 file
|
|
Extract {
|
|
/// Path to the XEX2 file
|
|
file: PathBuf,
|
|
/// Output path for the extracted PE file (default: same name with .exe extension)
|
|
output: Option<PathBuf>,
|
|
},
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
match cli.command {
|
|
Command::Inspect { file } => cmd_inspect(&file),
|
|
Command::Extract { file, output } => cmd_extract(&file, output),
|
|
}
|
|
}
|
|
|
|
fn cmd_inspect(path: &PathBuf) {
|
|
let data = read_file(path);
|
|
let xex = parse_xex(&data);
|
|
|
|
xex2tractor::display::display_header(&xex.header);
|
|
xex2tractor::display::display_optional_headers(&xex.optional_headers);
|
|
xex2tractor::display::display_security_info(&xex.security_info);
|
|
}
|
|
|
|
fn cmd_extract(path: &PathBuf, output: Option<PathBuf>) {
|
|
let output_path = output.unwrap_or_else(|| path.with_extension("exe"));
|
|
|
|
let data = read_file(path);
|
|
let xex = parse_xex(&data);
|
|
|
|
let pe_image = match xex2tractor::extract::extract_pe_image(&data, &xex) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
eprintln!("Error extracting PE image: {e}");
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
// TODO(M6): verify PE headers before writing
|
|
if let Err(e) = std::fs::write(&output_path, &pe_image) {
|
|
eprintln!("Error writing {}: {e}", output_path.display());
|
|
process::exit(1);
|
|
}
|
|
|
|
println!("Extracted PE image to {}", output_path.display());
|
|
println!(" Input: {} ({} bytes)", path.display(), data.len());
|
|
println!(" Output: {} ({} bytes)", output_path.display(), pe_image.len());
|
|
}
|
|
|
|
fn read_file(path: &PathBuf) -> Vec<u8> {
|
|
match std::fs::read(path) {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
eprintln!("Error reading {}: {e}", path.display());
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_xex(data: &[u8]) -> xex2tractor::Xex2File {
|
|
match xex2tractor::parse(data) {
|
|
Ok(x) => x,
|
|
Err(e) => {
|
|
eprintln!("Error parsing XEX2: {e}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|