mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
UPDATE NEWREG FROM MAIN
This commit is contained in:
@@ -92,7 +92,7 @@ If an output folder is not provided, extracted files will be saved in folder `_<
|
|||||||
|
|
||||||
## Panasonic Blu-Ray (PANA_DVD.FRM, PANA_ESD.FRM, PANAEUSB.FRM)
|
## Panasonic Blu-Ray (PANA_DVD.FRM, PANA_ESD.FRM, PANAEUSB.FRM)
|
||||||
**Used in:** Panasonic Blu-Ray Players and Recorders
|
**Used in:** Panasonic Blu-Ray Players and Recorders
|
||||||
**Notes:** **Depends on keys** - see keys.rs, and there is an issue with some ancient files not extracting correctly.
|
**Notes:** **Depends on keys** - see keys.rs (Included keys should work for 99% of players released in and before 2014, and some released in 2018), Note that there is currently an issue with MAIN in some very ancient files not extracting correctly.
|
||||||
|
|
||||||
## Philips UPG (Autorun.upg, 2SWU3TXV)
|
## Philips UPG (Autorun.upg, 2SWU3TXV)
|
||||||
**Used in:** Philips pre-TPVision TVs 200?-2013
|
**Used in:** Philips pre-TPVision TVs 200?-2013
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ pub fn is_epk2_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dy
|
|||||||
|
|
||||||
pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
file.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
|
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
|
||||||
|
|
||||||
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
|
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
|
||||||
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
|
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
|
||||||
|
|||||||
+89
-41
@@ -89,8 +89,10 @@ struct MainListEntry {
|
|||||||
checksum: u32, //checksum of this MAIN entrys' data
|
checksum: u32, //checksum of this MAIN entrys' data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD";
|
||||||
|
|
||||||
#[derive(BinRead)]
|
#[derive(BinRead)]
|
||||||
struct MainEntryHeader {
|
struct CompressedFileHeader {
|
||||||
#[br(count = 14)] _header_string: Vec<u8>, //EXTRHEADDRV \x01\x00
|
#[br(count = 14)] _header_string: Vec<u8>, //EXTRHEADDRV \x01\x00
|
||||||
compression_type_byte: u16,
|
compression_type_byte: u16,
|
||||||
decompressed_size: u32,
|
decompressed_size: u32,
|
||||||
@@ -103,12 +105,12 @@ struct MainEntryHeader {
|
|||||||
_checksum_flag: u8,
|
_checksum_flag: u8,
|
||||||
#[br(count = 19)] _unused: Vec<u8>,
|
#[br(count = 19)] _unused: Vec<u8>,
|
||||||
}
|
}
|
||||||
impl MainEntryHeader {
|
impl CompressedFileHeader {
|
||||||
fn compression_type(&self) -> &str {
|
fn compression_type(&self) -> &str {
|
||||||
if self.compression_type_byte == 0 {
|
if self.compression_type_byte == 0 {
|
||||||
return "Uncompressed"
|
return "Uncompressed"
|
||||||
} else if self.compression_type_byte == 1 {
|
} else if self.compression_type_byte == 1 {
|
||||||
return "GZIP + LZSS"
|
return "GZIP"
|
||||||
} else if self.compression_type_byte == 2 {
|
} else if self.compression_type_byte == 2 {
|
||||||
return "LZSS"
|
return "LZSS"
|
||||||
} else {
|
} else {
|
||||||
@@ -207,7 +209,9 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
|
|||||||
if file_entry.size == 0 && file_entry.offset == 0 {
|
if file_entry.size == 0 && file_entry.offset == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
file_entries.push(FileEntry { offset: file_entry.offset, base_offset: context.base_hdr_size });
|
if !file_entries.iter().any(|f| f.offset == file_entry.offset ){
|
||||||
|
file_entries.push(FileEntry { offset: file_entry.offset, base_offset: context.base_hdr_size });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -240,14 +244,20 @@ fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64
|
|||||||
let mut modules: Vec<ModuleEntry> = Vec::new();
|
let mut modules: Vec<ModuleEntry> = Vec::new();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
let entry: ModuleEntry = hdr_reader.read_le()?;
|
let mut entry: ModuleEntry = hdr_reader.read_le()?;
|
||||||
if !entry.is_valid() {break};
|
if !entry.is_valid() {break};
|
||||||
println!("Module {} - Name: {}, Version: {}, Platform: {}, ID: {}, Offset: {}, Size: {}",
|
println!("Module {} - Name: {}, Version: {}, Platform: {}, ID: {}, Offset: {}, Size: {}",
|
||||||
i + 1, entry.name(), entry.version(), entry.platform(), entry.id(), entry.offset, entry.size);
|
i + 1, entry.name(), entry.version(), entry.platform(), entry.id(), entry.offset, entry.size);
|
||||||
if modules.iter().any(|m| m.name() == entry.name()){
|
if modules.iter().any(|m| m.offset == entry.offset ){
|
||||||
println!("- Duplicate module, skipping!");
|
println!("- Duplicate module, skipping!");
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//prevent collision of modules with the same name
|
||||||
|
if modules.iter().any(|m| m.name() == entry.name() ){
|
||||||
|
entry.name_bytes = format!("{}({})", entry.name(), i + 1).as_bytes().to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
modules.push(entry);
|
modules.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,14 +273,19 @@ fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64
|
|||||||
file_reader.seek(SeekFrom::Start(rel_offset))?;
|
file_reader.seek(SeekFrom::Start(rel_offset))?;
|
||||||
|
|
||||||
if module.name() == "MAIN" {
|
if module.name() == "MAIN" {
|
||||||
println!("Extracting MAIN...");
|
println!("- Extracting MAIN...");
|
||||||
extract_main(file_reader, key, output_path)?;
|
extract_main(file_reader, key, output_path)?;
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = common::read_exact(file_reader, module.size as usize)?;
|
let data = common::read_exact(file_reader, module.size as usize)?;
|
||||||
println!("- Decrypting...");
|
println!("- Decrypting...");
|
||||||
let dec_data = decrypt_data(&data, &key);
|
let mut dec_data = decrypt_data(&data, &key);
|
||||||
|
|
||||||
|
if module.name().starts_with("DRV") {
|
||||||
|
println!("- Extracting DRIVE firmware...");
|
||||||
|
dec_data = extract_drv(dec_data, &key)?;
|
||||||
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&output_folder)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
@@ -284,9 +299,12 @@ fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64
|
|||||||
|
|
||||||
fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let main_list_hdr: MainListHeader = file_reader.read_le()?;
|
let main_list_hdr: MainListHeader = file_reader.read_le()?;
|
||||||
println!("MAIN - Entry count: {}, Decompressed part size: {}", main_list_hdr.entry_count(), main_list_hdr.decompressed_part_size);
|
if main_list_hdr.entry_count() > 200 {
|
||||||
assert!(main_list_hdr.entry_count() < 200, "Unreasonable MAIN entry count!");
|
println!("Unsupported MAIN data, skipping!");
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("MAIN - Entry count: {}, Decompressed part size: {}", main_list_hdr.entry_count(), main_list_hdr.decompressed_part_size);
|
||||||
let mut main_entries: Vec<MainListEntry> = Vec::new();
|
let mut main_entries: Vec<MainListEntry> = Vec::new();
|
||||||
for i in 0..main_list_hdr.entry_count() {
|
for i in 0..main_list_hdr.entry_count() {
|
||||||
let main_entry: MainListEntry = file_reader.read_le()?;
|
let main_entry: MainListEntry = file_reader.read_le()?;
|
||||||
@@ -313,37 +331,9 @@ fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: Pa
|
|||||||
data.copy_from_slice(&decrypted);
|
data.copy_from_slice(&decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data_reader = Cursor::new(data);
|
print!("\nMAIN ({}/{}) - ", maine_i, main_entries.len());
|
||||||
let header: MainEntryHeader = data_reader.read_le()?;
|
let decompressed_data = decompress_data(&data)?;
|
||||||
println!("\nMAIN ({}/{}) - Compressed size: {}, Decompressed size: {}, Compression type: {}({})",
|
|
||||||
maine_i, main_entries.len(), header.compressed_size, header.decompressed_size, header.compression_type_byte, header.compression_type());
|
|
||||||
let compressed_data = common::read_exact(&mut data_reader, header.compressed_size as usize)?;
|
|
||||||
|
|
||||||
let decompressed_data;
|
|
||||||
if header.compression_type_byte == 1 { //gzip + lzss
|
|
||||||
println!("- (1/2) Decompressing GZIP...");
|
|
||||||
let decompressed_gzip = decompress_gzip(&compressed_data)?;
|
|
||||||
// the decompressed data has another header
|
|
||||||
let mut decompressed_gzip_reader = Cursor::new(decompressed_gzip);
|
|
||||||
let header: MainEntryHeader = decompressed_gzip_reader.read_le()?;
|
|
||||||
println!("- (2/2) Decompressing LZSS... (Compressed size: {}, Decompressed size: {})", header.compressed_size, header.decompressed_size);
|
|
||||||
let compressed_lzss = common::read_exact(&mut decompressed_gzip_reader, header.compressed_size as usize)?;
|
|
||||||
decompressed_data = decompress_lzss(&compressed_lzss);
|
|
||||||
assert!(decompressed_data.len() == header.decompressed_size as usize, "Decompressed size does not match size in header, decompression failed!");
|
|
||||||
|
|
||||||
} else if header.compression_type_byte == 2 { //only lzss
|
|
||||||
println!("- Decompressing LZSS...");
|
|
||||||
decompressed_data = decompress_lzss(&compressed_data);
|
|
||||||
assert!(decompressed_data.len() == header.decompressed_size as usize, "Decompressed size does not match size in header, decompression failed!");
|
|
||||||
|
|
||||||
} else if header.compression_type_byte == 0 { //no compression. havent encountered one yet
|
|
||||||
decompressed_data = compressed_data;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
println!("- Unknown compression method!");
|
|
||||||
decompressed_data = compressed_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(&output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(&output_path)?;
|
||||||
out_file.write_all(&decompressed_data)?;
|
out_file.write_all(&decompressed_data)?;
|
||||||
|
|
||||||
@@ -351,4 +341,62 @@ fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress_data(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
let mut data_reader = Cursor::new(data);
|
||||||
|
let header: CompressedFileHeader = data_reader.read_le()?;
|
||||||
|
println!("Compressed size: {}, Decompressed size: {}, Compression type: {}({})",
|
||||||
|
header.compressed_size, header.decompressed_size, header.compression_type_byte, header.compression_type());
|
||||||
|
|
||||||
|
let compressed_data = common::read_exact(&mut data_reader, header.compressed_size as usize)?;
|
||||||
|
let decompressed_data;
|
||||||
|
|
||||||
|
if header.compression_type_byte == 1 { //gzip + optionally lzss
|
||||||
|
println!("- Decompressing GZIP...");
|
||||||
|
let decompressed_gzip = decompress_gzip(&compressed_data)?;
|
||||||
|
|
||||||
|
// the decompressed data can have another header
|
||||||
|
if decompressed_gzip.starts_with(COMPRESSED_FILE_MAGIC) {
|
||||||
|
decompressed_data = decompress_data(&decompressed_gzip)?;
|
||||||
|
} else {
|
||||||
|
decompressed_data = decompressed_gzip;
|
||||||
|
}
|
||||||
|
} else if header.compression_type_byte == 2 { //only lzss
|
||||||
|
println!("- Decompressing LZSS...");
|
||||||
|
decompressed_data = decompress_lzss(&compressed_data);
|
||||||
|
if decompressed_data.len() != header.decompressed_size as usize {
|
||||||
|
return Err("Decompressed size does not match size in header, decompression failed!".into());
|
||||||
|
}
|
||||||
|
} else if header.compression_type_byte == 0 { //no compression. havent encountered one yet
|
||||||
|
decompressed_data = compressed_data;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
println!("- Unknown compression method!");
|
||||||
|
decompressed_data = compressed_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(decompressed_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_drv(mut data: Vec<u8>, key: &[u8; 8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
let data_size = data.len();
|
||||||
|
let decrypt_size: usize = 10240;
|
||||||
|
let header_size = 0x20;
|
||||||
|
|
||||||
|
//decrypt first and last 10240b (execpt last 48b)
|
||||||
|
let first_decrypted = decrypt_data(&data[..decrypt_size], &key);
|
||||||
|
data[..decrypt_size].copy_from_slice(&first_decrypted);
|
||||||
|
|
||||||
|
let last_decrypted = decrypt_data(&data[data_size as usize - decrypt_size - 48..data_size - 48], &key);
|
||||||
|
data[data_size as usize - decrypt_size - 48..data_size - 48].copy_from_slice(&last_decrypted);
|
||||||
|
|
||||||
|
//can be compressed
|
||||||
|
if data[header_size..].starts_with(COMPRESSED_FILE_MAGIC) {
|
||||||
|
let decompressed = decompress_data(&data[header_size..])?;
|
||||||
|
data.truncate(header_size);
|
||||||
|
data.extend_from_slice(&decompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
}
|
}
|
||||||
+289
-160
@@ -1,4 +1,4 @@
|
|||||||
//sddl_dec 5.0
|
//sddl_dec 6.0
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use crate::{AppContext, formats::Format};
|
use crate::{AppContext, formats::Format};
|
||||||
pub fn format() -> Format {
|
pub fn format() -> Format {
|
||||||
@@ -6,7 +6,7 @@ pub fn format() -> Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, File, OpenOptions};
|
||||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::{BinRead, BinReaderExt};
|
||||||
|
|
||||||
@@ -14,109 +14,7 @@ use crate::utils::common;
|
|||||||
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
||||||
use crate::utils::compression::{decompress_zlib};
|
use crate::utils::compression::{decompress_zlib};
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn decipher(s: &[u8]) -> Vec<u8> {
|
||||||
struct SddlSecHeader {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //0x11, 0x22, 0x33, 0x44
|
|
||||||
#[br(count = 4)] _unused: Vec<u8>,
|
|
||||||
#[br(count = 4)] info_entries_count_str_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] module_entries_count_str_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] _unk: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl SddlSecHeader {
|
|
||||||
fn info_entry_count(&self) -> u32 {
|
|
||||||
let string = common::string_from_bytes(&self.info_entries_count_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
fn module_entries_count(&self) -> u32 {
|
|
||||||
let string = common::string_from_bytes(&self.module_entries_count_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct EntryHeader {
|
|
||||||
#[br(count = 12)] name_str_bytes: Vec<u8>,
|
|
||||||
#[br(count = 12)] size_str_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl EntryHeader {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_str_bytes)
|
|
||||||
}
|
|
||||||
fn size(&self) -> u64 {
|
|
||||||
let string = common::string_from_bytes(&self.size_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct ModuleHeader {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //0x11, 0x22, 0x33, 0x44
|
|
||||||
_unk1: u8,
|
|
||||||
_id: u8,
|
|
||||||
#[br(count = 10)] _unused: Vec<u8>,
|
|
||||||
#[br(count = 4)] _file_base_version: Vec<u8>,
|
|
||||||
#[br(count = 4)] _file_previous_version: Vec<u8>,
|
|
||||||
#[br(count = 4)] file_version: Vec<u8>,
|
|
||||||
#[br(count = 4)] _unused2: Vec<u8>,
|
|
||||||
_index: u16,
|
|
||||||
#[br(count = 2)] control_bytes: Vec<u8>,
|
|
||||||
compressed_data_size: u32,
|
|
||||||
_uncompressed_data_size: u32,
|
|
||||||
_checksum: u32,
|
|
||||||
}
|
|
||||||
impl ModuleHeader {
|
|
||||||
fn is_compressed(&self) -> bool {
|
|
||||||
self.control_bytes[0] == 0x3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct ContentHeader {
|
|
||||||
_magic1: u8,
|
|
||||||
#[br(count = 4)] dest_offset_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] source_offset_bytes: Vec<u8>,
|
|
||||||
size: u32,
|
|
||||||
_magic2: u8,
|
|
||||||
}
|
|
||||||
impl ContentHeader {
|
|
||||||
fn dest_offset(&self) -> u32 {
|
|
||||||
let first_byte;
|
|
||||||
if self.dest_offset_bytes[0] & 0xF0 == 0xD0 {
|
|
||||||
first_byte = self.dest_offset_bytes[0] & 0x0F;
|
|
||||||
} else {
|
|
||||||
first_byte = self.dest_offset_bytes[0];
|
|
||||||
}
|
|
||||||
u32::from_be_bytes([first_byte, self.dest_offset_bytes[1], self.dest_offset_bytes[2], self.dest_offset_bytes[3]])
|
|
||||||
}
|
|
||||||
fn source_offset(&self) -> u32 {
|
|
||||||
u32::from_be_bytes([0x00, self.source_offset_bytes[1], self.source_offset_bytes[2], self.source_offset_bytes[3]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEC_KEY: [u8; 16] = [
|
|
||||||
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
|
|
||||||
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
|
|
||||||
];
|
|
||||||
|
|
||||||
static DEC_IV: [u8; 16] = [
|
|
||||||
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
|
|
||||||
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn is_sddl_sec_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
|
||||||
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
|
||||||
|
|
||||||
let header = common::read_file(&file, 0, 32)?;
|
|
||||||
let deciph_header = decipher(&header);
|
|
||||||
if deciph_header.starts_with(b"\x11\x22\x33\x44") {
|
|
||||||
Ok(Some(Box::new(())))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//ported from original from https://nese.team/posts/justctf/
|
|
||||||
fn decipher(s: &[u8]) -> Vec<u8> {
|
|
||||||
let len_ = s.len();
|
let len_ = s.len();
|
||||||
let mut v3: u32 = 904;
|
let mut v3: u32 = 904;
|
||||||
let mut out = s.to_vec();
|
let mut out = s.to_vec();
|
||||||
@@ -157,81 +55,312 @@ fn decipher(s: &[u8]) -> Vec<u8> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- STRUCTURES --
|
||||||
|
// -- SECFILE --
|
||||||
|
|
||||||
|
pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct SecHeader {
|
||||||
|
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
key_id_str_bytes: [u8; 4], //"key_id", purpose unknown
|
||||||
|
grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file
|
||||||
|
prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files
|
||||||
|
_unused_or_reserved: [u8; 16], //not used, is zeros
|
||||||
|
}
|
||||||
|
impl SecHeader {
|
||||||
|
pub fn key_id(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.key_id_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
pub fn grp_num(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.grp_num_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
pub fn prg_num(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.prg_num_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static INFO_FILE_EXTENSION: &str = ".TXT";
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
name_str_bytes: [u8; 12],
|
||||||
|
size_str_bytes: [u8; 12],
|
||||||
|
}
|
||||||
|
impl FileHeader {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_str_bytes)
|
||||||
|
}
|
||||||
|
pub fn size(&self) -> u64 {
|
||||||
|
let string = common::string_from_bytes(&self.size_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- MODULE --
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ModuleComHeader { //"com_header"
|
||||||
|
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
_outer_maker_id: u8,
|
||||||
|
_outer_model_id: u8,
|
||||||
|
_inner_maker_id: u8,
|
||||||
|
_reserve1: u8,
|
||||||
|
_reserve2: u32,
|
||||||
|
_reserve3: u32,
|
||||||
|
_start_version: [u8; 4], //the first version that can upgrade to the new version
|
||||||
|
_end_version: [u8; 4], //the last version that can upgrade to the new version
|
||||||
|
_new_version: [u8; 4], //the new version, as in the version of the data in this module
|
||||||
|
_reserve4: u16,
|
||||||
|
_module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ModuleHeader { //"header", appears after com_header
|
||||||
|
_module_id: u16,
|
||||||
|
module_atr: u8,
|
||||||
|
_target_id: u8,
|
||||||
|
pub cmp_size: u32,
|
||||||
|
_org_size: u32,
|
||||||
|
_crc_value: u32,
|
||||||
|
}
|
||||||
|
impl ModuleHeader {
|
||||||
|
pub fn is_ciphered(&self) -> bool {
|
||||||
|
(self.module_atr & 0x02) != 0
|
||||||
|
}
|
||||||
|
pub fn is_compressed(&self) -> bool {
|
||||||
|
(self.module_atr & 0x01) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ContentHeader {
|
||||||
|
_magic1: u8, //always 0x01?
|
||||||
|
_dest_offset: u32,
|
||||||
|
_source_offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
_magic2: u8, //always 0x21?
|
||||||
|
}
|
||||||
|
impl ContentHeader {
|
||||||
|
//these hacks are needed because for some reason older files have the first nibble of the offset set to D/C
|
||||||
|
//no idea why, but masking them off makes it works properly
|
||||||
|
pub fn dest_offset(&self) -> u32 {
|
||||||
|
if ((self._dest_offset >> 28) & 0xF) == 0xD {
|
||||||
|
self._dest_offset & 0x0FFFFFFF
|
||||||
|
} else {
|
||||||
|
self._dest_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn source_offset(&self) -> u32 {
|
||||||
|
if ((self._source_offset >> 28) & 0xF) == 0xC {
|
||||||
|
self._source_offset & 0x0FFFFFFF
|
||||||
|
} else {
|
||||||
|
self._source_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn has_subfile(&self) -> bool {
|
||||||
|
self.source_offset() == 0x10E
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- TDI --
|
||||||
|
// Called SDIT.FDI in the secfile
|
||||||
|
|
||||||
|
pub static TDI_FILENAME: &str = "SDIT.FDI";
|
||||||
|
pub static SUPPORTED_TDI_VERSION: u16 = 2;
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiHead {
|
||||||
|
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
pub num_of_group: u8,
|
||||||
|
_reserve1: u8,
|
||||||
|
pub format_version: u16, //checks for "2" here
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiGroupHead {
|
||||||
|
pub group_id: u8,
|
||||||
|
pub num_of_target: u8, //logic checks that this is not more than 5
|
||||||
|
_reserved: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiTgtInf {
|
||||||
|
_outer_maker_id: u8,
|
||||||
|
_outer_model_id: u8,
|
||||||
|
_inner_maker_id: u8,
|
||||||
|
_reserve3: u8,
|
||||||
|
_inner_model_id: [u8; 4],
|
||||||
|
_ext_model_id: [u8; 4],
|
||||||
|
pub _start_version: [u8; 4], //the first version that can upgrade to the new version
|
||||||
|
pub _end_version: [u8; 4], //the last version that can upgrade to the new version
|
||||||
|
pub new_version: [u8; 4], //the new version, as in the version of the data in this module
|
||||||
|
pub target_id: u8,
|
||||||
|
_num_of_compatible_target: u8,
|
||||||
|
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
|
||||||
|
_unknown: [u8; 8],
|
||||||
|
module_name_bytes: [u8; 8],
|
||||||
|
}
|
||||||
|
impl TdiTgtInf {
|
||||||
|
pub fn module_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.module_name_bytes)
|
||||||
|
}
|
||||||
|
pub fn version_string(&self) -> String {
|
||||||
|
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- dec key --
|
||||||
|
static DEC_KEY: [u8; 16] = [
|
||||||
|
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
|
||||||
|
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
|
||||||
|
];
|
||||||
|
|
||||||
|
static DEC_IV: [u8; 16] = [
|
||||||
|
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
|
||||||
|
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn is_sddl_sec_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
let header = common::read_file(&file, 0, 32).expect("Failed to read from file.");
|
||||||
|
let deciph_header = decipher(&header);
|
||||||
|
if deciph_header.starts_with(b"\x11\x22\x33\x44") {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec<u8>), Box<dyn std::error::Error>> {
|
||||||
|
let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?);
|
||||||
|
let file_header: FileHeader = hdr_reader.read_be()?;
|
||||||
|
let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?;
|
||||||
|
|
||||||
|
Ok((file_header, file_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tdi_to_modules(tdi_data: Vec<u8>) -> Result<Vec<TdiTgtInf>, Box<dyn std::error::Error>> {
|
||||||
|
let mut tdi_reader = Cursor::new(tdi_data);
|
||||||
|
let tdi_header: TdiHead = tdi_reader.read_be()?;
|
||||||
|
if tdi_header.download_id != DOWNLOAD_ID {
|
||||||
|
return Err("Invalid TDI header!".into());
|
||||||
|
}
|
||||||
|
if tdi_header.format_version != SUPPORTED_TDI_VERSION {
|
||||||
|
return Err(format!("Unsupported TDI format version {}! (The supported version is {})", tdi_header.format_version, SUPPORTED_TDI_VERSION).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("[TDI] Group count: {}", tdi_header.num_of_group);
|
||||||
|
let mut modules: Vec<TdiTgtInf> = Vec::new();
|
||||||
|
|
||||||
|
for _i in 0..tdi_header.num_of_group {
|
||||||
|
let group_head: TdiGroupHead = tdi_reader.read_be()?;
|
||||||
|
println!("[TDI] Group ID: {}, Target count: {}", group_head.group_id, group_head.num_of_target);
|
||||||
|
|
||||||
|
for _i in 0..group_head.num_of_target {
|
||||||
|
let tgt_inf: TdiTgtInf = tdi_reader.read_be()?;
|
||||||
|
println!("[TDI] - {}, Target ID: {}, Segment count: {}, Version: {}",
|
||||||
|
tgt_inf.module_name(), tgt_inf.target_id, tgt_inf.num_of_txx, tgt_inf.version_string());
|
||||||
|
|
||||||
|
//push unique modules
|
||||||
|
if !modules.iter().any(|m| m.module_name() == tgt_inf.module_name()) {
|
||||||
|
modules.push(tgt_inf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(modules)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
file.seek(SeekFrom::Start(0))?;
|
||||||
|
let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
||||||
|
let secfile_header: SecHeader = secfile_hdr_reader.read_be()?;
|
||||||
|
|
||||||
let mut hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
|
||||||
let hdr: SddlSecHeader = hdr_reader.read_be()?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
|
|
||||||
//SDIT.FDI + info files + module files
|
let (tdi_file, tdi_data) = get_sec_file(&file)?;
|
||||||
let total_entry_count = 1 + hdr.info_entry_count() + hdr.module_entries_count();
|
println!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size());
|
||||||
println!("File info:\nInfo entry count: {}\nModule entry count: {}\nTotal entry count: {}",
|
//if save_extra { //Save SDIT
|
||||||
hdr.info_entry_count(), hdr.module_entries_count(), total_entry_count);
|
// let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&output_folder).join(tdi_file.name()))?;
|
||||||
|
// out_file.write_all(&tdi_data)?;
|
||||||
|
//}
|
||||||
|
if tdi_file.name() != TDI_FILENAME {
|
||||||
|
return Err(format!("Invalid TDI filename {}!, expected: {}", tdi_file.name(), TDI_FILENAME).into());
|
||||||
|
}
|
||||||
|
//parse TDI
|
||||||
|
let modules = parse_tdi_to_modules(tdi_data)?;
|
||||||
|
|
||||||
for i in 0..total_entry_count {
|
//get info files, each info file belongs to its respecitve group in the TDI
|
||||||
let mut entry_header_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?);
|
for i in 0..secfile_header.grp_num() {
|
||||||
let entry_header: EntryHeader = entry_header_reader.read_be()?;
|
let (info_file, info_data) = get_sec_file(&file)?;
|
||||||
|
println!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size());
|
||||||
|
if !info_file.name().ends_with(INFO_FILE_EXTENSION) {
|
||||||
|
return Err(format!("Info file {} does not have the expected extension {}!", info_file.name(), INFO_FILE_EXTENSION).into());
|
||||||
|
}
|
||||||
|
//if save_extra { //Save info file
|
||||||
|
// let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&output_folder).join(info_file.name()))?;
|
||||||
|
// out_file.write_all(&info_data)?;
|
||||||
|
//}
|
||||||
|
//print info file
|
||||||
|
println!("{}", String::from_utf8_lossy(&info_data));
|
||||||
|
}
|
||||||
|
|
||||||
println!("\n({}/{}) - {}, Size: {}", i + 1, total_entry_count, entry_header.name(), entry_header.size());
|
//parse module data
|
||||||
|
for (i, module) in modules.iter().enumerate(){
|
||||||
|
println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}",
|
||||||
|
i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string());
|
||||||
|
|
||||||
let data = common::read_exact(&mut file, entry_header.size() as usize)?;
|
for i in 0..module.num_of_txx {
|
||||||
let dec_data = decrypt_aes128_cbc_pcks7(&data, &DEC_KEY, &DEC_IV)?;
|
let (module_file, module_data) = get_sec_file(&file)?;
|
||||||
|
if !module_file.name().starts_with(&module.module_name()) {
|
||||||
|
return Err(format!("Module file {} does not start with the module's name: {}!", module_file.name(), module.module_name()).into());
|
||||||
|
}
|
||||||
|
println!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size());
|
||||||
|
|
||||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
let mut module_reader = Cursor::new(module_data);
|
||||||
//detect the file type based on the counts of each file
|
let com_header: ModuleComHeader = module_reader.read_be()?;
|
||||||
if i == 0 { //SDIT.FDI file
|
if com_header.download_id != DOWNLOAD_ID {
|
||||||
let output_path = Path::new(&app_ctx.output_dir).join(entry_header.name());
|
return Err("Invalid module com_header!".into());
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
|
||||||
out_file.write_all(&dec_data)?;
|
|
||||||
println!("-- Saved file!");
|
|
||||||
|
|
||||||
} else if i - 1 < hdr.info_entry_count() { //.TXT info file
|
|
||||||
println!("{}", String::from_utf8_lossy(&dec_data));
|
|
||||||
continue
|
|
||||||
|
|
||||||
} else { //Module file
|
|
||||||
let name = entry_header.name();
|
|
||||||
let source_name = name.split(".").next().unwrap();
|
|
||||||
|
|
||||||
let mut module_reader = Cursor::new(dec_data);
|
|
||||||
let module_header: ModuleHeader = module_reader.read_be()?;
|
|
||||||
println!("- Version: {}.{}{}{}", module_header.file_version[0], module_header.file_version[1], module_header.file_version[2], module_header.file_version[3]);
|
|
||||||
|
|
||||||
let module_data = common::read_exact(&mut module_reader, module_header.compressed_data_size as usize)?;
|
|
||||||
println!("- Deciphering...");
|
|
||||||
let deciphered_data = decipher(&module_data);
|
|
||||||
|
|
||||||
let content: Vec<u8>;
|
|
||||||
if module_header.is_compressed() {
|
|
||||||
println!("-- Decompressing...");
|
|
||||||
content = decompress_zlib(&deciphered_data)?;
|
|
||||||
} else {
|
|
||||||
println!("-- Uncompressed...");
|
|
||||||
content = deciphered_data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut content_reader = Cursor::new(content);
|
let module_header: ModuleHeader = module_reader.read_be()?;
|
||||||
|
let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?;
|
||||||
|
if module_header.is_ciphered() {
|
||||||
|
println!(" - Deciphering...");
|
||||||
|
module_data = decipher(&module_data);
|
||||||
|
}
|
||||||
|
if module_header.is_compressed() {
|
||||||
|
println!(" - Decompressing...");
|
||||||
|
module_data = decompress_zlib(&module_data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content_reader = Cursor::new(module_data);
|
||||||
let content_header: ContentHeader = content_reader.read_be()?;
|
let content_header: ContentHeader = content_reader.read_be()?;
|
||||||
|
println!(" --> 0x{:X} @ 0x{:X}", content_header.size, content_header.dest_offset());
|
||||||
|
|
||||||
|
let output_path: PathBuf;
|
||||||
|
if content_header.has_subfile() {
|
||||||
|
let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?;
|
||||||
|
let sub_filename = common::string_from_bytes(&sub_filename_bytes);
|
||||||
|
println!(" --> {}", sub_filename);
|
||||||
|
|
||||||
let output_path: PathBuf;
|
let sub_folder_path = Path::new(&app_ctx.output_dir).join(module.module_name());
|
||||||
if content_header.source_offset() == 270 {
|
fs::create_dir_all(&sub_folder_path)?;
|
||||||
let file_name_bytes = common::read_exact(&mut content_reader, 256)?;
|
output_path = Path::new(&sub_folder_path).join(sub_filename);
|
||||||
let file_name = common::string_from_bytes(&file_name_bytes);
|
|
||||||
println!("--- File name: {}", file_name);
|
|
||||||
|
|
||||||
let out_folder_path = Path::new(&app_ctx.output_dir).join(source_name);
|
|
||||||
fs::create_dir_all(&out_folder_path)?;
|
|
||||||
output_path = Path::new(&out_folder_path).join(file_name);
|
|
||||||
} else {
|
} else {
|
||||||
output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", source_name));
|
output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", module.module_name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = common::read_exact(&mut content_reader, content_header.size as usize)?;
|
let data = common::read_exact(&mut content_reader, content_header.size as usize)?;
|
||||||
|
|
||||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
||||||
out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?;
|
out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?;
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
println!("--- Saved!");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ pub static PFLUPG: &[(&str, &str)] = &[
|
|||||||
//pana dvd keys (no AES)
|
//pana dvd keys (no AES)
|
||||||
pub static PANA_DVD_KEYONLY: &[&str] = &[
|
pub static PANA_DVD_KEYONLY: &[&str] = &[
|
||||||
("08E03D859AF9F3EE"), //B3A25A0B9D864F08 # 87 firmwares, ..-~2011
|
("08E03D859AF9F3EE"), //B3A25A0B9D864F08 # 87 firmwares, ..-~2011
|
||||||
|
("AF4D5B16C298C16D"), //720FCB305F2F6EDA # 3 firmwares, ~2011
|
||||||
|
("9407311B7561F97E"), //FBCC4CEA55466CA6 # 12 firmwares, ~2012
|
||||||
|
("38BB0C17D362701F"), //8AC6E0CFF65651F5 # 8 firmwares, ~2013
|
||||||
|
("EC238CD791456646"), //5FF6D2A1B8675D6F # 3 firmwares, ~2013
|
||||||
|
("8F8CA88482E43D9B"), //918C319720ACFEC1 # 5 firmwares, ~2014
|
||||||
|
("21FCA17361E66B4E"), //542FD3A55F200FD5 # 5 firmwares, ~2014
|
||||||
];
|
];
|
||||||
|
|
||||||
//pana dvd keys (PANAEUSB pair - AES key, AES IV, cust)
|
//pana dvd keys (PANAEUSB pair - AES key, AES IV, cust)
|
||||||
|
|||||||
Reference in New Issue
Block a user