mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
PANA_DVD extractor initial added.
This commit is contained in:
Generated
+7
@@ -697,6 +697,12 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -781,6 +787,7 @@ dependencies = [
|
||||
"md5",
|
||||
"rsa",
|
||||
"sha1",
|
||||
"simd-adler32",
|
||||
"tar",
|
||||
]
|
||||
|
||||
|
||||
+2
-1
@@ -16,4 +16,5 @@ rsa = { version = "0.9", features = ["hazmat"] }
|
||||
hex = "0.4"
|
||||
ecb = "0.1"
|
||||
tar = "0.4.44"
|
||||
binrw = "0.15"
|
||||
binrw = "0.15"
|
||||
simd-adler32 = "*"
|
||||
@@ -11,6 +11,7 @@ pub mod roku;
|
||||
pub mod sony_bdp;
|
||||
pub mod rvp;
|
||||
pub mod funai_upg;
|
||||
pub mod pana_dvd;
|
||||
|
||||
pub mod pup;
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{Write, Read, Cursor, Seek};
|
||||
|
||||
use simd_adler32::adler32;
|
||||
use flate2::read::GzDecoder;
|
||||
use binrw::{BinRead, BinReaderExt};
|
||||
|
||||
use crate::keys;
|
||||
use crate::common;
|
||||
use crate::utils::pana_dvd_crypto::{decrypt_data};
|
||||
use crate::utils::lzss::{decompress_lzss};
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct ModuleEntry {
|
||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
||||
#[br(count = 4)] version_bytes: Vec<u8>,
|
||||
_unk: u32,
|
||||
offset: u32,
|
||||
#[br(count = 8)] platform_bytes: Vec<u8>,
|
||||
_unk1: u16,
|
||||
#[br(count = 6)] id_bytes: Vec<u8>,
|
||||
size: u32,
|
||||
data_checksum: u32,
|
||||
_unk2: u32,
|
||||
_entry_checksum: u32,
|
||||
}
|
||||
impl ModuleEntry {
|
||||
fn name(&self) -> String {
|
||||
common::string_from_bytes(&self.name_bytes)
|
||||
}
|
||||
fn version(&self) -> String {
|
||||
common::string_from_bytes(&self.version_bytes)
|
||||
}
|
||||
fn platform(&self) -> String {
|
||||
common::string_from_bytes(&self.platform_bytes)
|
||||
}
|
||||
fn id(&self) -> String {
|
||||
common::string_from_bytes(&self.id_bytes)
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
self.name().is_ascii() && self.platform().is_ascii()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct MainListHeader {
|
||||
_checksum: u32,
|
||||
_unk: u32,
|
||||
list_size: u32,
|
||||
_unk2: u64,
|
||||
}
|
||||
impl MainListHeader {
|
||||
fn entry_count(&self) -> u32 {
|
||||
(&self.list_size - 20) / 8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct MainListEntry {
|
||||
size: u32,
|
||||
checksum: u32,
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct MainEntryHeader {
|
||||
#[br(count = 14)] _header_string: Vec<u8>, //EXTRHEADDRV
|
||||
compression_type_byte: u16,
|
||||
decompressed_size: u32,
|
||||
_destination_address: u32,
|
||||
compressed_size: u32,
|
||||
_unk: u32,
|
||||
_footer_offset: u32,
|
||||
_base_address: u32,
|
||||
_checksum: u32,
|
||||
_checksum_flag: u8,
|
||||
#[br(count = 19)] _unused: Vec<u8>,
|
||||
}
|
||||
impl MainEntryHeader {
|
||||
fn compression_type(&self) -> &str {
|
||||
if self.compression_type_byte == 0 {
|
||||
return "Uncompressed"
|
||||
} else if self.compression_type_byte == 1 {
|
||||
return "GZIP + LZSS"
|
||||
} else if self.compression_type_byte == 2 {
|
||||
return "LZSS"
|
||||
} else {
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8]) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
|
||||
for key_hex in key_array {
|
||||
let key_bytes = hex::decode(key_hex)?;
|
||||
let key_array: [u8; 8] = match key_bytes.as_slice().try_into() {
|
||||
Ok(k) => k,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let decrypted = decrypt_data(data, &key_array);
|
||||
|
||||
if decrypted.starts_with(expected_magic) {
|
||||
return Ok(Some(key_array));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn is_pana_dvd_file(mut file: &File) -> bool {
|
||||
let header = common::read_file(&file, 0, 16).expect("Failed to read from file.");
|
||||
if header.starts_with(b"PANASONIC\x00\x00\x00") {
|
||||
file.seek(std::io::SeekFrom::Start(48)).expect("Failed to seek"); //skip rest of header
|
||||
true
|
||||
} else if find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG").expect("Failed to decrypt header.").is_some() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn decompress_gzip(data: &[u8]) -> std::io::Result<Vec<u8>> {
|
||||
let mut decoder = GzDecoder::new(data);
|
||||
let mut decompressed = Vec::new();
|
||||
decoder.read_to_end(&mut decompressed)?;
|
||||
Ok(decompressed)
|
||||
}
|
||||
|
||||
pub fn extract_pana_dvd(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let enc_header = common::read_exact(&mut file, 8192)?;
|
||||
let matching_key;
|
||||
let header;
|
||||
// find the key, knowing that the first entry is always "PROG"
|
||||
if let Some(key_array) = find_key(&keys::PANA_DVD_KEYONLY, &enc_header, b"PROG")? {
|
||||
println!("Found valid key: {}\n", hex::encode_upper(key_array));
|
||||
matching_key = Some(key_array);
|
||||
header = decrypt_data(&enc_header, &key_array);
|
||||
} else {
|
||||
println!("No valid key found!\n");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let matching_key_array = matching_key.as_ref().unwrap();
|
||||
|
||||
let mut hdr_reader = Cursor::new(header);
|
||||
let mut modules: Vec<ModuleEntry> = Vec::new();
|
||||
|
||||
for i in 0..100 {
|
||||
let entry: ModuleEntry = hdr_reader.read_le()?;
|
||||
if !entry.is_valid() {break};
|
||||
println!("Module {} - Name: {}, Version: {}, Platform: {}, ID: {}, Offset: {}, Size: {}",
|
||||
i + 1, entry.name(), entry.version(), entry.platform(), entry.id(), entry.offset, entry.size);
|
||||
if modules.iter().any(|m| m.name() == entry.name()){
|
||||
println!("- Duplicate module, skipping!");
|
||||
continue
|
||||
}
|
||||
modules.push(entry);
|
||||
}
|
||||
|
||||
let mut main_data: Option<Vec<u8>> = None;
|
||||
for module in modules {
|
||||
println!("\nSave module {}, Offset: {}, Size: {}, Expected checksum: {:#010x}",
|
||||
module.name(), module.offset, module.size, module.data_checksum);
|
||||
|
||||
let mut data = common::read_file(&file, module.offset as u64, module.size as usize)?;
|
||||
let checksum = adler32(&data.as_slice());
|
||||
println!("Calculated checksum: {:#010x}", checksum);
|
||||
if checksum == module.data_checksum {
|
||||
println!("[OK] Checksum correct without decrypt");
|
||||
} else {
|
||||
println!("Checksum incorrect, try with decrypt...");
|
||||
let dec_data = decrypt_data(&data, &matching_key_array);
|
||||
let checksum = adler32(&dec_data.as_slice());
|
||||
println!("Calculated checksum: {:#010x}", checksum);
|
||||
if checksum == module.data_checksum {
|
||||
println!("[OK] Checksum correct with decrypt");
|
||||
data = dec_data;
|
||||
} else {
|
||||
println!("[NG] Checksum incorrect, unknown data")
|
||||
}
|
||||
}
|
||||
|
||||
if module.name() == "MAIN" {
|
||||
main_data = Some(data);
|
||||
println!("MAIN found - will extract later...");
|
||||
continue
|
||||
}
|
||||
|
||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", module.name()));
|
||||
|
||||
fs::create_dir_all(&output_folder)?;
|
||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&data)?;
|
||||
|
||||
println!("- Saved file!");
|
||||
|
||||
}
|
||||
|
||||
if !main_data.is_some() {
|
||||
println!("\nMAIN data not found, Extraction finished!");
|
||||
return Ok(())
|
||||
}
|
||||
println!("\nExtracting MAIN section...");
|
||||
|
||||
let main_data_unwrap = main_data.as_ref().unwrap();
|
||||
let mut main_reader = Cursor::new(main_data_unwrap);
|
||||
|
||||
let main_list_hdr: MainListHeader = main_reader.read_le()?;
|
||||
println!("MAIN entry count: {}", main_list_hdr.entry_count());
|
||||
|
||||
let mut main_entries: Vec<MainListEntry> = Vec::new();
|
||||
for i in 0..main_list_hdr.entry_count() {
|
||||
let main_entry: MainListEntry = main_reader.read_le()?;
|
||||
println!("- Entry {}/{} - Size: {}, Checksum: {:#010x}",
|
||||
i + 1, main_list_hdr.entry_count(), main_entry.size, main_entry.checksum);
|
||||
main_entries.push(main_entry);
|
||||
}
|
||||
|
||||
for entry in main_entries {
|
||||
let mut data = common::read_exact(&mut main_reader, entry.size as usize)?;
|
||||
//decrypt first and last 5kb
|
||||
let first_decrypted = decrypt_data(&data[..5120], &matching_key_array);
|
||||
data[..5120].copy_from_slice(&first_decrypted);
|
||||
|
||||
let last_decrypted = decrypt_data(&data[entry.size as usize - 5120..], &matching_key_array);
|
||||
data[entry.size as usize - 5120..].copy_from_slice(&last_decrypted);
|
||||
//
|
||||
|
||||
let mut data_reader = Cursor::new(data);
|
||||
let header: MainEntryHeader = data_reader.read_le()?;
|
||||
println!("\nSaving entry - 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 + 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);
|
||||
} else if header.compression_type_byte == 2 { //only lzss
|
||||
println!("- Decompressing LZSS...");
|
||||
decompressed_data = decompress_lzss(&compressed_data);
|
||||
} 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 output_path = Path::new(&output_folder).join("MAIN.bin");
|
||||
|
||||
fs::create_dir_all(&output_folder)?;
|
||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&decompressed_data)?;
|
||||
|
||||
println!("- Saved to MAIN!");
|
||||
}
|
||||
|
||||
println!("\nExtraction finished!");
|
||||
Ok(())
|
||||
}
|
||||
@@ -74,4 +74,9 @@ pub static EPK2: &[(&str, &str)] = &[
|
||||
pub static EPK3: &[(&str, &str)] = &[
|
||||
("34CC219D3AFC102433109BBC1DA44095", "m14 (m14tv) - LX webOS 1 (2014)"),
|
||||
("52A208FA24E7E70730A40999B1C22C148F4920484BC50B515D243E35D14689F1", "o24n - LX webOS 10 (2025)")
|
||||
];
|
||||
|
||||
//pana dvd keys (no AES)
|
||||
pub static PANA_DVD_KEYONLY: &[&str] = &[
|
||||
("08E03D859AF9F3EE"), //B3A25A0B9D864F08 # 87 devices, ..-~2011
|
||||
];
|
||||
@@ -103,6 +103,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("PFL UPG file detected!");
|
||||
formats::pfl_upg::extract_pfl_upg(&file, &output_path)?;
|
||||
}
|
||||
else if formats::pana_dvd::is_pana_dvd_file(&file) {
|
||||
println!("PANA_DVD file detected!");
|
||||
formats::pana_dvd::extract_pana_dvd(&file, &output_path)?;
|
||||
}
|
||||
else if formats::pup::is_pup_file(&file) {
|
||||
println!("PUP file detected!");
|
||||
formats::pup::extract_pup(&file, &output_path)?;
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
pub mod aes;
|
||||
pub mod aes;
|
||||
pub mod pana_dvd_crypto;
|
||||
pub mod lzss;
|
||||
@@ -0,0 +1,51 @@
|
||||
pub fn decompress_lzss(data: &[u8]) -> Vec<u8> {
|
||||
let mut window = [0u8; 0x1000];
|
||||
let mut dst = Vec::new();
|
||||
let mut src_i = 0;
|
||||
let mut win_pos = 0xFEE;
|
||||
let mut flags = 0u16;
|
||||
|
||||
while src_i < data.len() {
|
||||
flags >>= 1;
|
||||
if (flags & 0x100) == 0 {
|
||||
if src_i >= data.len() {
|
||||
break;
|
||||
}
|
||||
flags = data[src_i] as u16 | 0xFF00;
|
||||
src_i += 1;
|
||||
}
|
||||
|
||||
if (flags & 1) == 0 {
|
||||
// Backreference
|
||||
if src_i + 1 >= data.len() {
|
||||
break;
|
||||
}
|
||||
let b1 = data[src_i];
|
||||
let b2 = data[src_i + 1];
|
||||
src_i += 2;
|
||||
|
||||
let mut offset = (b1 as usize) | (((b2 & 0xF0) as usize) << 4);
|
||||
let length = ((b2 & 0x0F) as usize) + 3;
|
||||
|
||||
for _ in 0..length {
|
||||
let c = window[offset];
|
||||
dst.push(c);
|
||||
window[win_pos] = c;
|
||||
win_pos = (win_pos + 1) & 0xFFF;
|
||||
offset = (offset + 1) & 0xFFF;
|
||||
}
|
||||
} else {
|
||||
// Literal
|
||||
if src_i >= data.len() {
|
||||
break;
|
||||
}
|
||||
let c = data[src_i];
|
||||
src_i += 1;
|
||||
dst.push(c);
|
||||
window[win_pos] = c;
|
||||
win_pos = (win_pos + 1) & 0xFFF;
|
||||
}
|
||||
}
|
||||
|
||||
dst
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//libfmupre.so
|
||||
//DAT_29f06d60
|
||||
const SBOX: [u8; 256] = [
|
||||
0x00,0x01,0xbb,0x9c,0x3f,0x5e,0xc7,0x3e,0x36,0xa4,0x92,0x93,0x38,0x9b,0x8d,0x1a,
|
||||
0x3c,0x84,0xf7,0x2a,0xcb,0x3d,0x70,0xad,0x30,0xa2,0xc1,0x95,0x03,0x39,0xa5,0x0e,
|
||||
0xe6,0xfb,0x09,0x1e,0xb9,0xc4,0x24,0x57,0x5f,0xc3,0x5d,0xfa,0x60,0x56,0xfd,0xed,
|
||||
0x81,0xf3,0x4a,0xff,0x85,0xdb,0xcd,0xa0,0xd0,0x9d,0x8b,0x26,0x4c,0x50,0x0c,0x1d,
|
||||
0x7d,0x77,0xde,0x7a,0x0a,0x0b,0x73,0xe1,0x54,0x76,0xe8,0xc2,0x28,0x05,0x98,0x99,
|
||||
0x29,0x4b,0xee,0x87,0x42,0x59,0x65,0xb8,0x1f,0xdf,0x23,0x58,0x63,0x69,0x1c,0x79,
|
||||
0x64,0xc5,0x6f,0x13,0x3b,0xce,0x08,0xa7,0xb2,0x12,0x20,0x7f,0xe2,0x89,0x21,0x2b,
|
||||
0x41,0x49,0x7c,0x82,0xbe,0xf2,0x43,0x8e,0x86,0x02,0x9e,0xd8,0x67,0x8f,0xa3,0xe7,
|
||||
0xbc,0xf8,0x66,0x22,0x4d,0xe5,0xba,0xd2,0xda,0xea,0x61,0x25,0xb0,0xc0,0x7b,0xa6,
|
||||
0x48,0x96,0xdd,0xdc,0x71,0x90,0x55,0x5b,0x4f,0x4e,0x6d,0xd1,0x11,0x34,0xaa,0x1b,
|
||||
0xf4,0x17,0x80,0xb7,0xcc,0x8c,0xd9,0x37,0x8a,0x2c,0x94,0x16,0x72,0x40,0xef,0x9a,
|
||||
0xc8,0x47,0xf6,0xfc,0x2e,0xb3,0x2f,0xbd,0xcf,0xa9,0x15,0x10,0x18,0xec,0x6a,0x6e,
|
||||
0xc9,0x91,0x53,0x78,0xa8,0x3a,0xaf,0x0d,0xe0,0x97,0x32,0x51,0xb1,0xf5,0x27,0x2d,
|
||||
0x35,0x88,0x14,0xe3,0xfe,0x06,0xd7,0xd6,0xab,0xa1,0xd5,0xe4,0x45,0x44,0x9f,0x0f,
|
||||
0x5a,0x83,0xeb,0xca,0x07,0x04,0xb4,0xb6,0x52,0xae,0xd4,0x33,0x31,0x75,0xd3,0x6c,
|
||||
0x62,0xbf,0x6b,0xf9,0xac,0x46,0xf0,0x5c,0x19,0x7e,0x68,0xb5,0xf1,0x74,0xc6,0xe9,
|
||||
];
|
||||
|
||||
//FUN_29f05520
|
||||
fn round_function(state: &mut [u8; 4], a5: &[u8; 4]) {
|
||||
let v5 = a5[1] ^ state[1];
|
||||
let v6 = a5[2] ^ state[2];
|
||||
let v7 = state[3] ^ a5[3];
|
||||
|
||||
let v8 = ((v5 >> 7) | (v5 << 1)) & 0xFF;
|
||||
let v9 = ((v6 >> 5) | (v6 << 3)) & 0xFF;
|
||||
let v10 = a5[0] ^ state[0];
|
||||
let v11 = ((v7 >> 2) | (v7 << 6)) & 0xFF;
|
||||
let v12 = v10 ^ v9 ^ v8;
|
||||
|
||||
let temp_v7 = v10 >> 1;
|
||||
let temp_v10 = ((v10 << 7) | (v11 >> 1)) & 0xFF;
|
||||
|
||||
let new_a4 = ((v9 >> 1) | (v11 << 7)) & 0xFF;
|
||||
let v13 = SBOX[(v11 ^ v12) as usize];
|
||||
let new_a3 = ((v8 >> 1) | (v9 << 7)) & 0xFF;
|
||||
let new_a2 = (temp_v7 | (v8 << 7)) & 0xFF;
|
||||
let new_result = temp_v10 & 0xFF;
|
||||
|
||||
let a4_final = new_a4.wrapping_add(v13);
|
||||
let a3_final = new_a3.wrapping_add(((v13 >> 7) | (v13 << 1)) & 0xFF);
|
||||
let a2_final = new_a2.wrapping_add(((v13 >> 6) | (v13 << 2)) & 0xFF);
|
||||
let result_final = new_result.wrapping_add(((v13 >> 5) | (v13 << 3)) & 0xFF);
|
||||
|
||||
state[0] = result_final;
|
||||
state[1] = a2_final;
|
||||
state[2] = a3_final;
|
||||
state[3] = a4_final;
|
||||
}
|
||||
|
||||
//FUN_29f055dc
|
||||
fn key_schedule(xored_key: &[u8; 8], out: &mut [u8; 32]) {
|
||||
//in original function 2 keys were XORed together to make the actual key (that the round keys are derived from)
|
||||
// why? i dont know (obfuscation?) but i use the XORed key here directly
|
||||
let mut v26 = [0u8; 8];
|
||||
for i in 0..7 {
|
||||
v26[i] = xored_key[i];
|
||||
}
|
||||
let v12 = xored_key[7];
|
||||
v26[1] ^= v12;
|
||||
v26[4] ^= v12;
|
||||
|
||||
for i in 0..7 {
|
||||
out[i] = v26[i];
|
||||
}
|
||||
for i in 7..32 {
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
let mut v22: [u8; 4] = [53, 125, 251, 154];
|
||||
|
||||
for v16 in 0..16 {
|
||||
let v17 = 4 * (v16 & 7);
|
||||
let mut state: [u8; 4] = out[v17..v17 + 4].try_into().unwrap();
|
||||
round_function(&mut state, &v22);
|
||||
out[v17..v17 + 4].copy_from_slice(&state);
|
||||
v22.copy_from_slice(&state);
|
||||
}
|
||||
}
|
||||
|
||||
//FUN_29f056b0
|
||||
fn decrypt_block(a1: &[u8; 32], a2: &mut [u8; 8]) {
|
||||
let mut v6 = a2[0];
|
||||
let mut v7 = a2[1];
|
||||
let mut v5 = a2[2];
|
||||
let mut v4 = a2[3];
|
||||
|
||||
let mut v24 = v6;
|
||||
let mut v25 = v7;
|
||||
let mut v26 = v5;
|
||||
let mut v27 = v4;
|
||||
|
||||
let mut v10 = a2[4];
|
||||
let mut v11 = a2[5];
|
||||
let mut v12 = a2[6];
|
||||
let mut v13 = a2[7];
|
||||
|
||||
let mut v15: u8 = 0;
|
||||
let mut v16: u8 = 0;
|
||||
let mut v17: u8 = 0;
|
||||
let mut v18: u8 = 0;
|
||||
|
||||
for v2 in (0..16).rev() {
|
||||
let v14 = (v2 & 7) as usize;
|
||||
let subkey: [u8; 4] = a1[4 * v14..4 * v14 + 4].try_into().unwrap();
|
||||
let mut st = [v24, v25, v26, v27];
|
||||
round_function(&mut st, &subkey);
|
||||
v24 = st[0];
|
||||
v25 = st[1];
|
||||
v26 = st[2];
|
||||
v27 = st[3];
|
||||
|
||||
v15 = v10 ^ v24;
|
||||
v16 = v11 ^ v25;
|
||||
v17 = v12 ^ v26;
|
||||
v18 = v13 ^ v27;
|
||||
|
||||
v24 ^= v10;
|
||||
v25 ^= v11;
|
||||
v26 ^= v12;
|
||||
v27 ^= v13;
|
||||
|
||||
v10 = v6;
|
||||
v11 = v7;
|
||||
v12 = v5;
|
||||
v13 = v4;
|
||||
|
||||
if v2 == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
v6 = v15;
|
||||
v7 = v16;
|
||||
v5 = v17;
|
||||
v4 = v18;
|
||||
}
|
||||
|
||||
a2[0] = v6;
|
||||
a2[1] = v7;
|
||||
a2[2] = v5;
|
||||
a2[3] = v4;
|
||||
a2[4] = v15;
|
||||
a2[5] = v16;
|
||||
a2[6] = v17;
|
||||
a2[7] = v18;
|
||||
}
|
||||
|
||||
fn decrypt_data_inplace(data: &mut [u8], key: &[u8; 8]) {
|
||||
let mut subkeys = [0u8; 32];
|
||||
key_schedule(key, &mut subkeys);
|
||||
for chunk in data.chunks_mut(8) {
|
||||
if chunk.len() == 8 {
|
||||
let mut block: [u8; 8] = chunk.try_into().unwrap();
|
||||
decrypt_block(&subkeys, &mut block);
|
||||
chunk.copy_from_slice(&block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_data(data: &[u8], key: &[u8; 8]) -> Vec<u8> {
|
||||
let mut buf = data.to_vec();
|
||||
decrypt_data_inplace(&mut buf, key);
|
||||
buf
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
| Mediatek PKG | Many Mediatek-based TVs (Hisense, Sony, Panasonic, Philips...) | Only files with 144 byte header and using 4xVendor magic as enc key are supported | https://github.com/openlgtv/epk2extract |
|
||||
| Mediatek upgrade_loader | Older Mediatek-based TVs | All files should be supported providing they are not encrypted | - |
|
||||
| Novatek pkg | Some Novatek-based TVs (Philips, LG..) | All files should be supported | https://github.com/openlgtv/epk2extract |
|
||||
| Panasonic Blu-Ray (PANA_DVD) | Panasonic Blu-Ray Players/Recorders | Only some files are supported (**depends on keys**) | - |
|
||||
| Philips UPG | Philips pre-TPVision TVS 2008-2013 | Only some files are supported (**depends on keys**) | https://github.com/frederic/pflupg-tool |
|
||||
| PUP | Sony PlayStation 4/ PlayStation 5 | File has to be decrypted | https://github.com/Zer0xFF/ps4-pup-unpacker |
|
||||
| RUF | Samsung Broadcom-based Blu-Ray players | Only BD-P1600 is supported (**depends on keys**) | - |
|
||||
|
||||
Reference in New Issue
Block a user