feat: add PE image decompression and extraction pipeline (M5)
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>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use xex2tractor::crypto;
|
||||
use xex2tractor::extract;
|
||||
use xex2tractor::header::{ModuleFlags, XEX2_MAGIC};
|
||||
use xex2tractor::optional::{CompressionInfo, CompressionType, EncryptionType, SystemFlags};
|
||||
use xex2tractor::security::{ImageFlags, MediaFlags, RegionFlags};
|
||||
@@ -311,16 +312,100 @@ fn test_cli_inspect_missing_file() {
|
||||
assert!(!output.status.success());
|
||||
}
|
||||
|
||||
// ── Extraction tests ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_cli_extract_not_yet_implemented() {
|
||||
fn test_extract_pe_image() {
|
||||
let data = sample_data();
|
||||
let xex = xex2tractor::parse(&data).unwrap();
|
||||
|
||||
let pe_image = extract::extract_pe_image(&data, &xex).unwrap();
|
||||
|
||||
// Output size should match security_info.image_size
|
||||
assert_eq!(pe_image.len(), xex.security_info.image_size as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_pe_starts_with_mz() {
|
||||
let data = sample_data();
|
||||
let xex = xex2tractor::parse(&data).unwrap();
|
||||
|
||||
let pe_image = extract::extract_pe_image(&data, &xex).unwrap();
|
||||
|
||||
// PE image must start with MZ signature (0x4D5A)
|
||||
assert_eq!(pe_image[0], 0x4D, "first byte should be 'M'");
|
||||
assert_eq!(pe_image[1], 0x5A, "second byte should be 'Z'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_pe_has_valid_pe_header() {
|
||||
let data = sample_data();
|
||||
let xex = xex2tractor::parse(&data).unwrap();
|
||||
|
||||
let pe_image = extract::extract_pe_image(&data, &xex).unwrap();
|
||||
|
||||
// Read e_lfanew from DOS header (offset 0x3C, little-endian per PE spec)
|
||||
let e_lfanew = u32::from_le_bytes([
|
||||
pe_image[0x3C],
|
||||
pe_image[0x3D],
|
||||
pe_image[0x3E],
|
||||
pe_image[0x3F],
|
||||
]) as usize;
|
||||
|
||||
// PE signature at e_lfanew: "PE\0\0"
|
||||
assert_eq!(&pe_image[e_lfanew..e_lfanew + 4], b"PE\0\0");
|
||||
|
||||
// Machine type at e_lfanew + 4: 0x01F2 (IMAGE_FILE_MACHINE_POWERPCBE, little-endian)
|
||||
let machine = u16::from_le_bytes([pe_image[e_lfanew + 4], pe_image[e_lfanew + 5]]);
|
||||
assert_eq!(machine, 0x01F2, "machine should be POWERPCBE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_extract_writes_file() {
|
||||
let path = format!("{}/tests/data/default.xex", env!("CARGO_MANIFEST_DIR"));
|
||||
let output_path = format!("{}/target/test_extract_output.exe", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
// Clean up any previous test output
|
||||
let _ = std::fs::remove_file(&output_path);
|
||||
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_xex2tractor"))
|
||||
.args(["extract", &path, &output_path])
|
||||
.output()
|
||||
.expect("failed to run xex2tractor");
|
||||
|
||||
assert!(output.status.success(), "CLI extract should succeed");
|
||||
|
||||
// Verify output file exists and starts with MZ
|
||||
let extracted = std::fs::read(&output_path).expect("should be able to read extracted file");
|
||||
assert!(extracted.len() > 2);
|
||||
assert_eq!(extracted[0], 0x4D); // 'M'
|
||||
assert_eq!(extracted[1], 0x5A); // 'Z'
|
||||
|
||||
// Clean up
|
||||
let _ = std::fs::remove_file(&output_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_extract_default_output_path() {
|
||||
let path = format!("{}/tests/data/default.xex", env!("CARGO_MANIFEST_DIR"));
|
||||
let expected_output = format!("{}/tests/data/default.exe", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
// Clean up any previous test output
|
||||
let _ = std::fs::remove_file(&expected_output);
|
||||
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_xex2tractor"))
|
||||
.args(["extract", &path])
|
||||
.output()
|
||||
.expect("failed to run xex2tractor");
|
||||
|
||||
// Extract should fail with "not yet implemented" for now
|
||||
assert!(!output.status.success());
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(stderr.contains("not yet implemented"));
|
||||
assert!(output.status.success(), "CLI extract should succeed");
|
||||
|
||||
// Verify default output path was used
|
||||
assert!(
|
||||
std::fs::metadata(&expected_output).is_ok(),
|
||||
"default output file should exist"
|
||||
);
|
||||
|
||||
// Clean up
|
||||
let _ = std::fs::remove_file(&expected_output);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user