use xex2tractor::header::{ModuleFlags, XEX2_MAGIC}; use xex2tractor::optional::{CompressionType, EncryptionType, SystemFlags}; fn sample_data() -> Vec { let path = format!("{}/tests/data/default.xex", env!("CARGO_MANIFEST_DIR")); std::fs::read(&path).expect("sample file should exist at tests/data/default.xex") } // ── Header tests ────────────────────────────────────────────────────────────── #[test] fn test_full_parse() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); assert_eq!(xex.header.magic, XEX2_MAGIC); assert_eq!(xex.header.module_flags, ModuleFlags(0x00000001)); assert_eq!(xex.header.header_size, 0x00003000); assert_eq!(xex.header.reserved, 0x00000000); assert_eq!(xex.header.security_offset, 0x00000090); assert_eq!(xex.header.header_count, 15); } #[test] fn test_parse_empty_file() { let data = vec![]; assert!(xex2tractor::parse(&data).is_err()); } #[test] fn test_parse_invalid_magic() { let mut data = sample_data(); data[0] = 0x00; assert!(xex2tractor::parse(&data).is_err()); } // ── Optional header tests ───────────────────────────────────────────────────── #[test] fn test_optional_headers_all_present() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let opt = &xex.optional_headers; // All 15 entries should be parsed assert_eq!(opt.entries.len(), 15); // Verify presence of all expected headers assert!(opt.entry_point.is_some()); assert!(opt.image_base_address.is_some()); assert!(opt.default_stack_size.is_some()); assert!(opt.system_flags.is_some()); assert!(opt.execution_info.is_some()); assert!(opt.file_format_info.is_some()); assert!(opt.checksum_timestamp.is_some()); assert!(opt.original_pe_name.is_some()); assert!(opt.tls_info.is_some()); assert!(opt.static_libraries.is_some()); assert!(opt.import_libraries.is_some()); assert!(opt.resource_info.is_some()); assert!(opt.game_ratings.is_some()); assert!(opt.lan_key.is_some()); assert!(opt.xbox360_logo_size.is_some()); } #[test] fn test_optional_inline_values() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let opt = &xex.optional_headers; assert_eq!(opt.entry_point.unwrap(), 0x824AB748); assert_eq!(opt.image_base_address.unwrap(), 0x82000000); assert_eq!(opt.default_stack_size.unwrap(), 0x00080000); assert_eq!(opt.system_flags.unwrap(), SystemFlags(0x00000400)); } #[test] fn test_optional_execution_info() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let exec = xex.optional_headers.execution_info.as_ref().unwrap(); assert_eq!(exec.title_id, 0x535107D4); assert_eq!(exec.media_id, 0x2D2E2EEB); } #[test] fn test_optional_file_format() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let fmt = xex.optional_headers.file_format_info.as_ref().unwrap(); assert_eq!(fmt.encryption_type, EncryptionType::Normal); assert_eq!(fmt.compression_type, CompressionType::Normal); } #[test] fn test_optional_static_libraries() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let libs = xex.optional_headers.static_libraries.as_ref().unwrap(); assert_eq!(libs.len(), 12); // Verify first and a few known libraries assert_eq!(libs[0].name, "XAPILIB"); assert_eq!(libs[1].name, "D3D9"); assert_eq!(libs[3].name, "XBOXKRNL"); } #[test] fn test_optional_import_libraries() { let data = sample_data(); let xex = xex2tractor::parse(&data).unwrap(); let imports = xex.optional_headers.import_libraries.as_ref().unwrap(); assert_eq!(imports.string_table.len(), 2); assert_eq!(imports.string_table[0], "xam.xex"); assert_eq!(imports.string_table[1], "xboxkrnl.exe"); assert!(!imports.libraries.is_empty()); } // ── CLI tests ───────────────────────────────────────────────────────────────── #[test] fn test_cli_runs_with_sample() { let path = format!("{}/tests/data/default.xex", env!("CARGO_MANIFEST_DIR")); let output = std::process::Command::new(env!("CARGO_BIN_EXE_xex2tractor")) .arg(&path) .output() .expect("failed to run xex2tractor"); assert!(output.status.success(), "CLI should exit successfully"); let stdout = String::from_utf8_lossy(&output.stdout); // Header section assert!(stdout.contains("XEX2 Header")); assert!(stdout.contains("0x58455832")); assert!(stdout.contains("TITLE")); assert!(stdout.contains("Header Count: 15")); // Optional headers section assert!(stdout.contains("Optional Headers (15 entries)")); assert!(stdout.contains("[ENTRY_POINT] 0x824AB748")); assert!(stdout.contains("[IMAGE_BASE_ADDRESS] 0x82000000")); assert!(stdout.contains("EXECUTION_INFO")); assert!(stdout.contains("0x535107D4")); // title ID assert!(stdout.contains("FILE_FORMAT_INFO")); assert!(stdout.contains("Normal (AES-128-CBC)")); assert!(stdout.contains("Normal (LZX)")); assert!(stdout.contains("STATIC_LIBRARIES")); assert!(stdout.contains("XAPILIB")); assert!(stdout.contains("IMPORT_LIBRARIES")); assert!(stdout.contains("xboxkrnl.exe")); assert!(stdout.contains("default.pe")); // original PE name assert!(stdout.contains("PAL50_INCOMPATIBLE")); // system flags } #[test] fn test_cli_no_args() { let output = std::process::Command::new(env!("CARGO_BIN_EXE_xex2tractor")) .output() .expect("failed to run xex2tractor"); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("Usage")); } #[test] fn test_cli_missing_file() { let output = std::process::Command::new(env!("CARGO_BIN_EXE_xex2tractor")) .arg("/nonexistent/file.xex") .output() .expect("failed to run xex2tractor"); assert!(!output.status.success()); }