feat: add AES-128-CBC decryption and clap CLI (M4)

Add session key derivation and payload decryption using AES-128-CBC
with well-known XEX2 master keys. Refactor CLI to use clap with
inspect/extract subcommands. Extend FileFormatInfo to parse
compression metadata (basic blocks, LZX window size/block chain).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-03-28 21:31:31 +01:00
parent 38d1cc1b6d
commit df26b028b6
9 changed files with 711 additions and 32 deletions

View File

@@ -1,31 +1,77 @@
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 path = match std::env::args().nth(1) {
Some(p) => p,
None => {
eprintln!("Usage: xex2tractor <file.xex>");
process::exit(1);
}
};
let cli = Cli::parse();
let data = match std::fs::read(&path) {
Ok(d) => d,
Err(e) => {
eprintln!("Error reading {path}: {e}");
process::exit(1);
}
};
match cli.command {
Command::Inspect { file } => cmd_inspect(&file),
Command::Extract { file, output } => cmd_extract(&file, output),
}
}
let xex = match xex2tractor::parse(&data) {
Ok(x) => x,
Err(e) => {
eprintln!("Error parsing XEX2: {e}");
process::exit(1);
}
};
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);
// TODO(M5): decrypt + decompress pipeline
// TODO(M6): verify PE and write to output_path
eprintln!("Error: extraction not yet implemented (coming in M5/M6)");
process::exit(1);
}
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);
}
}
}