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

@@ -3,6 +3,7 @@ use crate::header::Xex2Header;
use crate::optional::{
format_hex_bytes, format_rating, format_timestamp, HeaderKey, OptionalHeaders,
};
use crate::security::SecurityInfo;
/// Prints the XEX2 main header in a human-readable format.
pub fn display_header(header: &Xex2Header) {
@@ -200,3 +201,91 @@ pub fn display_optional_headers(headers: &OptionalHeaders) {
}
}
}
/// Prints the security info in a human-readable format.
pub fn display_security_info(security: &SecurityInfo) {
println!();
println!("=== Security Info ===");
println!(
"Header Size: 0x{:08X} ({} bytes)",
security.header_size, security.header_size
);
println!(
"Image Size: 0x{:08X} ({} bytes)",
security.image_size, security.image_size
);
// RSA signature — show first 8 and last 8 bytes
let sig = &security.rsa_signature;
println!(
"RSA Signature: {}...{} (256 bytes)",
sig[..4].iter().map(|b| format!("{b:02X}")).collect::<String>(),
sig[252..].iter().map(|b| format!("{b:02X}")).collect::<String>()
);
println!("Unknown (0x108): 0x{:08X}", security.unk_108);
println!("Image Flags: {}", security.image_flags);
println!("Load Address: 0x{:08X}", security.load_address);
println!(
"Section Digest: {}",
format_hex_bytes(&security.section_digest)
);
println!("Import Table Count: {}", security.import_table_count);
println!(
"Import Table Digest: {}",
format_hex_bytes(&security.import_table_digest)
);
println!(
"XGD2 Media ID: {}",
security
.xgd2_media_id
.iter()
.map(|b| format!("{b:02X}"))
.collect::<String>()
);
println!(
"AES Key (encrypted): {}",
format_hex_bytes(&security.aes_key)
);
if security.export_table == 0 {
println!("Export Table: 0x00000000 (none)");
} else {
println!("Export Table: 0x{:08X}", security.export_table);
}
println!(
"Header Digest: {}",
format_hex_bytes(&security.header_digest)
);
println!("Region: {}", security.region);
println!("Allowed Media Types: {}", security.allowed_media_types);
// Page descriptors
println!();
let page_size = security.image_flags.page_size();
let page_size_label = if page_size == 0x1000 { "4KB" } else { "64KB" };
println!(
"Page Descriptors ({} entries, {} pages):",
security.page_descriptor_count, page_size_label
);
let mut address_offset: u64 = 0;
for (i, desc) in security.page_descriptors.iter().enumerate() {
let digest_preview: String = desc.data_digest[..6]
.iter()
.map(|b| format!("{b:02X}"))
.collect();
let size = desc.page_count as u64 * page_size as u64;
println!(
" #{i:<4} {:<10} {:<4} pages ({:>8} bytes) offset +0x{address_offset:08X} SHA1: {digest_preview}...",
desc.section_type.to_string(),
desc.page_count,
size
);
address_offset += size;
}
println!(
" Total mapped size: 0x{address_offset:X} ({address_offset} bytes)"
);
}