mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
improvements to invincible_image
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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<dyn Any>) -> 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<Entry> = 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(())
|
||||
|
||||
Reference in New Issue
Block a user