diff --git a/src/formats/invincible_image/include.rs b/src/formats/invincible_image/include.rs index 5faa69d..a0b97af 100644 --- a/src/formats/invincible_image/include.rs +++ b/src/formats/invincible_image/include.rs @@ -7,26 +7,22 @@ pub static V3_IV: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0 #[derive(BinRead)] pub struct Header { - _magic_bytes: [u8; 16], - pub file_version: [u8; 4], - _unk1: u32, - ver1_bytes: [u8; 16], - ver2_bytes: [u8; 16], - _unk2: u16, - _type: u8, - pub keep_size: u32, - _unk3: u8, + _magic_bytes: [u8; 16], // INVINCIBLE_IMAGE + pub file_infos: [u8; 8], // 0=format version/key id, 1,2,3=unknown, 4-7=unused/reserved + ver1_bytes: [u8; 16], // actual firmware version + ver2_bytes: [u8; 16], // Netcast platform version + _unk1: [u8; 3], + pub chunk_size: u32, + pub chunk_count: u8, // 0 means not chunked pub data_start_offset: u32, pub data_size: u32, - _data_size_2: u32, - pub skip_size: u32, - _unk4: u16, - _encryption_method: u8, // 0x01 - AES128, 0x02 - AES256 - _hash_type: u8, // 0x01 - MD5, 0x02 - SHA1 - ver3_bytes: [u8; 16], - ver4_bytes: [u8; 16], - _unk6: [u8; 11], - pub payload_count: u8, + _file_size: u32, + pub signature_size: u32, + _flags: [u8; 4], // 0,1=unknown, 2=Encryption method, 3=Hash method + ver3_bytes: [u8; 16], // model like in ROM filename, or sometimes just "A"? + ver4_bytes: [u8; 16], // LG SDK version + _unk2: [u8; 11], + pub payload_count: u8, // if 0, entire data is a single payload } impl Header { pub fn ver1(&self) -> String { diff --git a/src/formats/invincible_image/mod.rs b/src/formats/invincible_image/mod.rs index 8b49fb7..e399ae0 100644 --- a/src/formats/invincible_image/mod.rs +++ b/src/formats/invincible_image/mod.rs @@ -7,7 +7,7 @@ use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Seek, SeekFrom, Cursor}; use binrw::BinReaderExt; -use crate::utils::aes::{decrypt_aes128_cbc_nopad}; +use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; use crate::utils::common; use include::*; @@ -26,54 +26,62 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box) -> Res let mut file = app_ctx.file().ok_or("Extractor expected file")?; let header: Header = file.read_le()?; - println!("File info:\nFile Version: {}.{}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nData start offset: {}\nKeep data size: {}\nSkip data size: {}\n\nPayload Count: {}", - header.file_version[0], header.file_version[1], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.data_start_offset, header.keep_size, header.skip_size, header.payload_count); + println!("File info -\nFile Version: {}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nChunk count: {}\nChunk size: {}\n\nPayload Count: {}", + header.file_infos[0], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.chunk_count, header.chunk_size, header.payload_count); let mut entries: Vec = Vec::new(); for i in 0..header.payload_count { let entry: Entry = file.read_le()?; - println!("{}. {}, Start offset: {}, Size: {}", + println!("{}. {} - Start offset: {}, Size: {}", i + 1, entry.name(), entry.start_offset, entry.size); entries.push(entry); } - if header.file_version[0] != 3 { - return Err("Unsupported format version! (Only 3 is supported right now)".into()); - } - - let mut encrypted_data = Vec::new(); - let mut buffer = vec![0u8; header.keep_size as usize]; + let (aes_key, aes_iv) = match header.file_infos[0] { + 3 => (V3_KEY, V3_IV), + _ => return Err("Unsupported format version! (Unknown key)".into()) + }; file.seek(SeekFrom::Start(header.data_start_offset.into()))?; - loop { - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 { - break; // EOF + let mut encrypted_data = Vec::with_capacity(header.data_size as usize); + + if header.chunk_count == 0 { //not chunked, read all data + encrypted_data = common::read_exact(&mut file, header.data_size as usize)?; + + } else { + let mut buffer = vec![0u8; header.chunk_size as usize]; + let mut remain = header.data_size as usize; + + for _ in 0..header.chunk_count { + let read_size = remain.min(buffer.len()); + let bytes_read = file.read(&mut buffer[..read_size])?; + encrypted_data.extend_from_slice(&buffer[..bytes_read]); + remain -= bytes_read; + + file.seek(SeekFrom::Current(header.signature_size.into()))?; // skip signature in each chunk } - encrypted_data.extend_from_slice(&buffer[..bytes_read]); - file.seek(SeekFrom::Current(header.skip_size.into()))?; } println!("\nDecrypting data..."); - let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &V3_KEY, &V3_IV)?; + let decrypted_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &aes_key, &aes_iv)?; let mut data_reader = Cursor::new(decrypted_data); - let mut i = 1; - for entry in entries { - println!("\n({}/{}) - {}, Size: {}", i, header.payload_count, entry.name(), entry.size); + for (i , entry) in entries.iter().enumerate() { + println!("\n({}/{}) - {}, Size: {}, Start offset: {}", i+1, header.payload_count, entry.name(), entry.size, entry.start_offset); + let data = common::read_exact(&mut data_reader, entry.size as usize)?; let output_path = Path::new(&app_ctx.output_dir).join(entry.name() + ".bin"); - fs::create_dir_all(&app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + + out_file.seek(SeekFrom::Start(entry.start_offset.into()))?; out_file.write_all(&data)?; println!("- Saved file!"); - i += 1; } Ok(())