/// Pretty-print formatting for parsed XEX2 structures. use crate::header::Xex2Header; use crate::optional::{ format_hex_bytes, format_rating, format_timestamp, CompressionInfo, HeaderKey, OptionalHeaders, }; use crate::security::SecurityInfo; /// Prints the XEX2 main header in a human-readable format. pub fn display_header(header: &Xex2Header) { println!("=== XEX2 Header ==="); println!("Magic: XEX2 (0x{:08X})", header.magic); println!("Module Flags: {}", header.module_flags); println!( "Header Size: 0x{:08X} ({} bytes)", header.header_size, header.header_size ); println!("Reserved: 0x{:08X}", header.reserved); println!("Security Offset: 0x{:08X}", header.security_offset); println!("Header Count: {}", header.header_count); } /// Prints all parsed optional headers in a human-readable format. pub fn display_optional_headers(headers: &OptionalHeaders) { println!(); println!("=== Optional Headers ({} entries) ===", headers.entries.len()); // Display inline u32 values first if let Some(v) = headers.entry_point { println!(); println!("[ENTRY_POINT] 0x{v:08X}"); } if let Some(v) = headers.original_base_address { println!("[ORIGINAL_BASE_ADDRESS] 0x{v:08X}"); } if let Some(v) = headers.image_base_address { println!("[IMAGE_BASE_ADDRESS] 0x{v:08X}"); } if let Some(v) = headers.default_stack_size { println!("[DEFAULT_STACK_SIZE] 0x{v:08X} ({v} bytes)"); } if let Some(v) = headers.default_filesystem_cache_size { println!("[DEFAULT_FILESYSTEM_CACHE_SIZE] 0x{v:08X} ({v} bytes)"); } if let Some(v) = headers.default_heap_size { println!("[DEFAULT_HEAP_SIZE] 0x{v:08X} ({v} bytes)"); } if let Some(v) = headers.title_workspace_size { println!("[TITLE_WORKSPACE_SIZE] 0x{v:08X} ({v} bytes)"); } if let Some(v) = headers.additional_title_memory { println!("[ADDITIONAL_TITLE_MEMORY] 0x{v:08X} ({v} bytes)"); } if let Some(v) = headers.enabled_for_fastcap { println!("[ENABLED_FOR_FASTCAP] 0x{v:08X}"); } // System flags if let Some(ref flags) = headers.system_flags { println!("[SYSTEM_FLAGS] {flags}"); } // Execution info if let Some(ref exec) = headers.execution_info { println!(); println!("[EXECUTION_INFO]"); println!(" Media ID: 0x{:08X}", exec.media_id); println!(" Title ID: 0x{:08X}", exec.title_id); println!(" Version: {}", exec.version); println!(" Base Version: {}", exec.base_version); println!(" Platform: {}", exec.platform); println!(" Executable Type: {}", exec.executable_type); println!(" Disc: {}/{}", exec.disc_number, exec.disc_count); println!(" Savegame ID: 0x{:08X}", exec.savegame_id); } // File format info if let Some(ref fmt) = headers.file_format_info { println!(); println!("[FILE_FORMAT_INFO]"); println!(" Encryption: {}", fmt.encryption_type); println!(" Compression: {}", fmt.compression_type); match &fmt.compression_info { CompressionInfo::Basic { blocks } => { println!(" Blocks: {} basic compression blocks", blocks.len()); } CompressionInfo::Normal { window_size, first_block, } => { println!( " Window Size: 0x{window_size:X} ({} KB)", window_size / 1024 ); println!( " First Block: {} bytes", first_block.block_size ); } _ => {} } } // Checksum + timestamp if let Some(ref ct) = headers.checksum_timestamp { println!(); println!("[CHECKSUM_TIMESTAMP]"); println!(" Checksum: 0x{:08X}", ct.checksum); println!( " Timestamp: 0x{:08X} ({})", ct.timestamp, format_timestamp(ct.timestamp) ); } // Original PE name if let Some(ref name) = headers.original_pe_name { println!(); println!("[ORIGINAL_PE_NAME] \"{name}\""); } // Bounding path if let Some(ref path) = headers.bounding_path { println!("[BOUNDING_PATH] \"{path}\""); } // TLS info if let Some(ref tls) = headers.tls_info { println!(); println!("[TLS_INFO]"); println!(" Slot Count: {}", tls.slot_count); println!(" Raw Data Address: 0x{:08X}", tls.raw_data_address); println!(" Data Size: {} bytes", tls.data_size); println!(" Raw Data Size: {} bytes", tls.raw_data_size); } // Static libraries if let Some(ref libs) = headers.static_libraries { println!(); println!("[STATIC_LIBRARIES] ({} libraries)", libs.len()); for lib in libs { println!(" {lib}"); } } // Import libraries if let Some(ref imports) = headers.import_libraries { println!(); println!("[IMPORT_LIBRARIES] ({} libraries)", imports.libraries.len()); for lib in &imports.libraries { println!( " {} v{} (min v{}) - {} imports", lib.name, lib.version, lib.version_min, lib.record_count ); } } // Resource info if let Some(ref resources) = headers.resource_info { println!(); println!("[RESOURCE_INFO] ({} entries)", resources.len()); for res in resources { println!( " \"{}\" @ 0x{:08X}, size: {} bytes", res.name, res.address, res.size ); } } // Game ratings if let Some(ref ratings) = headers.game_ratings { println!(); println!("[GAME_RATINGS]"); println!(" ESRB: {} | PEGI: {} | PEGI-FI: {} | PEGI-PT: {}", format_rating(ratings.esrb), format_rating(ratings.pegi), format_rating(ratings.pegi_fi), format_rating(ratings.pegi_pt)); println!(" BBFC: {} | CERO: {} | USK: {} | OFLC-AU: {}", format_rating(ratings.bbfc), format_rating(ratings.cero), format_rating(ratings.usk), format_rating(ratings.oflc_au)); println!(" OFLC-NZ: {} | KMRB: {} | Brazil: {} | FPB: {}", format_rating(ratings.oflc_nz), format_rating(ratings.kmrb), format_rating(ratings.brazil), format_rating(ratings.fpb)); } // LAN key if let Some(ref key) = headers.lan_key { println!(); println!("[LAN_KEY] {}", format_hex_bytes(key)); } // Callcap imports if let Some(ref callcap) = headers.enabled_for_callcap { println!(); println!("[ENABLED_FOR_CALLCAP]"); println!(" Start Thunk: 0x{:08X}", callcap.start_func_thunk_addr); println!(" End Thunk: 0x{:08X}", callcap.end_func_thunk_addr); } // Exports by name if let Some(ref dir) = headers.exports_by_name { println!(); println!("[EXPORTS_BY_NAME]"); println!(" Offset: 0x{:08X}", dir.offset); println!(" Size: {} bytes", dir.size); } // Xbox 360 logo if let Some(size) = headers.xbox360_logo_size { println!(); println!("[XBOX360_LOGO] {size} bytes"); } // Unknown headers for entry in &headers.entries { if let HeaderKey::Unknown(raw) = entry.key { println!(); println!( "[UNKNOWN(0x{raw:08X})] value/offset: 0x{:08X}", entry.value ); } } } /// 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::(), sig[252..].iter().map(|b| format!("{b:02X}")).collect::() ); 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::() ); 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)" ); }