mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
add onkyo extractor
This commit is contained in:
@@ -135,7 +135,14 @@ Tip: if you have split ROM (.ROM-00 and .ROM-01), extract both into the same fol
|
||||
|
||||
## Novatek TIMG
|
||||
**Used in:** Newer Novatek-based TVs (Philips(TPVision), Hisense, TCL...)
|
||||
**Notes:** None, all files should be supported.
|
||||
**Notes:** None, all files should be supported.
|
||||
|
||||
## Onkyo
|
||||
**Used in:** Onkyo AVRs and other AV devices
|
||||
**Notes:** Newer files seem to use a different encryption and are not (yet) supported.
|
||||
**Thanks to:** http://divideoverflow.com/2014/04/decrypting-onkyo-firmware-files/
|
||||
**Options:**
|
||||
※ Support `dump_dec_hdrs` option
|
||||
|
||||
## Panasonic Blu-Ray (PANA_DVD.FRM, PANA_ESD.FRM, PANAEUSB.FRM)
|
||||
**Used in:** Panasonic Blu-Ray Players and Recorders
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod sdboot;
|
||||
pub mod sdimage;
|
||||
pub mod cd5;
|
||||
pub mod gx_dvb;
|
||||
pub mod onkyo;
|
||||
|
||||
pub mod pup;
|
||||
|
||||
@@ -226,6 +227,11 @@ pub fn get_registry() -> Vec<Format> {
|
||||
detector_func: crate::formats::gx_dvb::is_gx_dvb_file,
|
||||
extractor_func: crate::formats::gx_dvb::extract_gx_dvb,
|
||||
},
|
||||
Format {
|
||||
name: "onkyo",
|
||||
detector_func: crate::formats::onkyo::is_onkyo_file,
|
||||
extractor_func: crate::formats::onkyo::extract_onkyo,
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use super::include::ONKYO_MAGIC;
|
||||
|
||||
pub fn ub_encrypte_block(input: &[u8], key: &[u8; 8]) -> Vec<u8> {
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
let state_counter = 0;
|
||||
let state_byte = 0;
|
||||
|
||||
let mut u_var3: u8 = if state_counter == 0 {
|
||||
key[0]
|
||||
} else {
|
||||
state_byte
|
||||
};
|
||||
|
||||
let mut u_var4: u32 = (state_counter % 7) + 1;
|
||||
let mut u_var7: u32 = state_counter;
|
||||
|
||||
for &byte in input {
|
||||
output.push(u_var3 ^ byte);
|
||||
|
||||
let pb_var1 = key[u_var4 as usize];
|
||||
u_var7 = u_var7.wrapping_add(1);
|
||||
|
||||
if (u_var4 + 1) * 0x10000 < 0x70001 {
|
||||
u_var4 = (u_var4 + 1) & 0xFFFF;
|
||||
} else {
|
||||
u_var4 = 1;
|
||||
}
|
||||
|
||||
let rot = ((u_var3 >> 7) | ((u_var3 & 0x7F) << 1)) & 0xFF;
|
||||
|
||||
u_var3 = pb_var1
|
||||
.wrapping_add(rot)
|
||||
.wrapping_add((u_var7 >> 6) as u8);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn calc_key(ciphertext: &[u8]) -> [u8; 8] {
|
||||
let mut key = [0u8; 8];
|
||||
|
||||
let mut ks = ONKYO_MAGIC[0] ^ ciphertext[0];
|
||||
key[0] = ks;
|
||||
|
||||
for j in 1..8 {
|
||||
let rol = (ks >> 7) | ((ks & 0x7f) << 1);
|
||||
ks = ONKYO_MAGIC[j] ^ ciphertext[j];
|
||||
|
||||
key[j] = ks.wrapping_sub(rol);
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::utils::common;
|
||||
use binrw::BinRead;
|
||||
|
||||
pub static ONKYO_MAGIC: &[u8] = b"ONKYO Encryption";
|
||||
pub static HEADER_KEY: [u8; 8] = [0xDA, 0x57, 0x68, 0x0D, 0x44, 0x21, 0x30, 0x7A];
|
||||
pub static DATA_KEY: [u8; 8] = [0xAE, 0xB7, 0x31, 0x74, 0x47, 0xE4, 0xFB, 0x5D];
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct Header {
|
||||
_magic_bytes: [u8;16], //ONKYO Encryption
|
||||
_header_size: u32,
|
||||
_header_checksum: u32,
|
||||
pub pack_info_offset: u32,
|
||||
pub ids_versions_offset: u32,
|
||||
pub table_offset: u32,
|
||||
_pad: [u8;12],
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct PackInfo {
|
||||
package_id_bytes: [u8;32],
|
||||
package_version_bytes: [u8;4],
|
||||
pub entry_count: u8, //maximum number of entries that can be in the file
|
||||
pub pack_count: u8, //how many .of* files there are
|
||||
pub pack_id: u8, //which .of* file this is
|
||||
pub entries_in_file: u8, //how many entries are actually in the file
|
||||
}
|
||||
impl PackInfo {
|
||||
pub fn package_id(&self) -> String {
|
||||
common::string_from_bytes(&self.package_id_bytes)
|
||||
}
|
||||
pub fn package_version(&self) -> String {
|
||||
common::string_from_bytes(&self.package_version_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct IDsVersionsEntry {
|
||||
pub pack_location: u8, //in which .of* file(pack) this entry is
|
||||
id_bytes: [u8;7],
|
||||
}
|
||||
impl IDsVersionsEntry {
|
||||
pub fn id(&self) -> String {
|
||||
common::string_from_bytes(&self.id_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct TableEntry {
|
||||
pub size: u32,
|
||||
pub offset: u32,
|
||||
pub checksum: u32,
|
||||
_pad: [u8;4],
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
mod include;
|
||||
mod crypto;
|
||||
use std::any::Any;
|
||||
use crate::AppContext;
|
||||
use crate::utils::global::opt_dump_dec_hdr;
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use binrw::BinReaderExt;
|
||||
|
||||
use crate::utils::common;
|
||||
use include::*;
|
||||
use crypto::*;
|
||||
|
||||
struct OnkyoCtx {
|
||||
header_size: u32,
|
||||
}
|
||||
|
||||
pub fn is_onkyo_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 enc_inihdr = common::read_file(&file, 0, 20)?;
|
||||
let dec_inihdr= ub_encrypte_block(&enc_inihdr, &HEADER_KEY);
|
||||
|
||||
if dec_inihdr.starts_with(ONKYO_MAGIC) {
|
||||
let header_size = u32::from_le_bytes(dec_inihdr[16..20].try_into().unwrap());
|
||||
Ok(Some(Box::new(OnkyoCtx {header_size})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_onkyo(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 ctx = ctx.downcast::<OnkyoCtx>().expect("Missing context");
|
||||
|
||||
println!("Header size: {}", ctx.header_size);
|
||||
|
||||
let enc_hdr = common::read_exact(&mut file, ctx.header_size as usize)?;
|
||||
let dec_hdr = ub_encrypte_block(&enc_hdr, &HEADER_KEY);
|
||||
opt_dump_dec_hdr(app_ctx, &dec_hdr, "header")?;
|
||||
let mut hdr_rdr = Cursor::new(dec_hdr);
|
||||
|
||||
let hdr: Header = hdr_rdr.read_le()?;
|
||||
|
||||
//read info section
|
||||
hdr_rdr.seek(SeekFrom::Start(hdr.pack_info_offset as u64))?;
|
||||
let info: PackInfo = hdr_rdr.read_le()?;
|
||||
println!("Info -\nPackage ID: {}\nVersion: {}\nEntry count: {}\nEntries in file: {}\nPack: {}/{}",
|
||||
info.package_id(), info.package_version(), info.entry_count, info.entries_in_file, info.pack_id, info.pack_count,);
|
||||
|
||||
if info.entries_in_file == 0 {
|
||||
return Err("There is nothing to extract in this pack".into()) //should this be an error?
|
||||
}
|
||||
|
||||
//..."IDsVersions" section
|
||||
hdr_rdr.seek(SeekFrom::Start(hdr.ids_versions_offset as u64))?;
|
||||
let mut entries: Vec<IDsVersionsEntry> = Vec::new();
|
||||
for _ in 0..info.entry_count {
|
||||
let entry: IDsVersionsEntry = hdr_rdr.read_le()?;
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
//...table section
|
||||
hdr_rdr.seek(SeekFrom::Start(hdr.table_offset as u64))?;
|
||||
let mut data_entries: Vec<TableEntry> = Vec::new();
|
||||
for _ in 0..info.entry_count {
|
||||
let entry: TableEntry = hdr_rdr.read_le()?;
|
||||
data_entries.push(entry);
|
||||
}
|
||||
|
||||
//
|
||||
let mut act_ei = 0;
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
let data_entry = &data_entries[i];
|
||||
|
||||
if entry.pack_location == 0 || (data_entry.offset == 0 && data_entry.checksum == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
act_ei += 1;
|
||||
println!("\n({}/{}) - {}, Size: {}, Offset: {}, Pack location: {}",
|
||||
act_ei, info.entries_in_file, entry.id(), data_entry.size, data_entry.offset, entry.pack_location);
|
||||
|
||||
let data = common::read_file(&mut file, data_entry.offset as u64, data_entry.size as usize)?;
|
||||
let mut out_data;
|
||||
|
||||
// -- try to decrypt --
|
||||
let mut dec_key: Option<[u8; 8]> = None;
|
||||
let mut is_pack = false;
|
||||
|
||||
//try using standard data key
|
||||
if ub_encrypte_block(&data[..16], &DATA_KEY).starts_with(ONKYO_MAGIC) {
|
||||
dec_key = Some(DATA_KEY);
|
||||
|
||||
//try header key, if success it means this is a package inside a package, and it should not be decrypted
|
||||
} else if ub_encrypte_block(&data[..16], &HEADER_KEY).starts_with(ONKYO_MAGIC) {
|
||||
is_pack = true;
|
||||
|
||||
//if not matched with data key, try to calc key
|
||||
} else {
|
||||
let calced_key = calc_key(&data[..8]);
|
||||
|
||||
if ub_encrypte_block(&data[..16], &calced_key).starts_with(ONKYO_MAGIC) {
|
||||
dec_key = Some(calced_key);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = dec_key {
|
||||
println!("- Detected encrypted data, decrypting...");
|
||||
out_data = ub_encrypte_block(&data, &key);
|
||||
out_data.drain(0..16); //remove ONKYO Encryption heading
|
||||
|
||||
} else if is_pack {
|
||||
println!("- Inner pack detected!"); //maybe handle this...
|
||||
out_data = data;
|
||||
|
||||
} else {
|
||||
println!("- Failed to decrypt data or entry is not encrypted, saving raw data...");
|
||||
out_data = data;
|
||||
}
|
||||
|
||||
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}_{}.bin", act_ei, entry.id()));
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&out_data)?;
|
||||
|
||||
println!("-- Saved file!");
|
||||
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user