feat: parse and display security info (M3)

Implement security info parsing including RSA signature, encrypted AES
key, image/region/media flags, load address, SHA-1 digests, and page
descriptors with section type classification. Add comprehensive unit
and integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-03-28 19:04:41 +01:00
parent b1f90a55b6
commit 66e078363c
8 changed files with 696 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
use xex2tractor::header::{ModuleFlags, XEX2_MAGIC};
use xex2tractor::optional::{CompressionType, EncryptionType, SystemFlags};
use xex2tractor::security::{ImageFlags, MediaFlags, RegionFlags};
fn sample_data() -> Vec<u8> {
let path = format!("{}/tests/data/default.xex", env!("CARGO_MANIFEST_DIR"));
@@ -120,6 +121,70 @@ fn test_optional_import_libraries() {
assert!(!imports.libraries.is_empty());
}
// ── Security info tests ───────────────────────────────────────────────────────
#[test]
fn test_security_info_parsed() {
let data = sample_data();
let xex = xex2tractor::parse(&data).unwrap();
let sec = &xex.security_info;
assert_eq!(sec.header_size, 0x00000F34);
assert_eq!(sec.image_size, 0x00920000);
assert_eq!(sec.unk_108, 0x00000174);
assert_eq!(sec.load_address, 0x82000000);
assert_eq!(sec.import_table_count, 2);
assert_eq!(sec.export_table, 0);
}
#[test]
fn test_security_flags() {
let data = sample_data();
let xex = xex2tractor::parse(&data).unwrap();
let sec = &xex.security_info;
assert_eq!(sec.image_flags, ImageFlags(0x00000008));
assert_eq!(sec.region, RegionFlags(0xFFFFFFFF));
assert_eq!(sec.allowed_media_types, MediaFlags(0x00000004));
}
#[test]
fn test_security_page_descriptors() {
let data = sample_data();
let xex = xex2tractor::parse(&data).unwrap();
let sec = &xex.security_info;
assert_eq!(sec.page_descriptor_count, 146);
assert_eq!(sec.page_descriptors.len(), 146);
// First descriptor has page_count = 19
assert_eq!(sec.page_descriptors[0].page_count, 19);
// Page size should be 64KB (4KB flag is not set)
assert_eq!(sec.image_flags.page_size(), 0x10000);
// Each page descriptor should have a valid page_count
for desc in &sec.page_descriptors {
assert!(desc.page_count > 0, "page_count should be positive");
}
}
#[test]
fn test_security_crypto_fields() {
let data = sample_data();
let xex = xex2tractor::parse(&data).unwrap();
let sec = &xex.security_info;
// RSA signature starts with 2C94EBE6
assert_eq!(sec.rsa_signature[0..4], [0x2C, 0x94, 0xEB, 0xE6]);
// XGD2 media ID starts with 3351
assert_eq!(sec.xgd2_media_id[0..2], [0x33, 0x51]);
// AES key starts with EACB
assert_eq!(sec.aes_key[0..2], [0xEA, 0xCB]);
}
// ── CLI tests ─────────────────────────────────────────────────────────────────
#[test]
@@ -154,6 +219,15 @@ fn test_cli_runs_with_sample() {
assert!(stdout.contains("xboxkrnl.exe"));
assert!(stdout.contains("default.pe")); // original PE name
assert!(stdout.contains("PAL50_INCOMPATIBLE")); // system flags
// Security info section
assert!(stdout.contains("Security Info"));
assert!(stdout.contains("0x00000F34")); // header size
assert!(stdout.contains("0x00920000")); // image size
assert!(stdout.contains("XGD2_MEDIA_ONLY")); // image flags
assert!(stdout.contains("ALL REGIONS")); // region
assert!(stdout.contains("DVD_CD")); // media type
assert!(stdout.contains("Page Descriptors")); // page descriptors section
}
#[test]