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:
88
src/main.rs
88
src/main.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user