add onkyo extractor

This commit is contained in:
theubusu
2026-05-01 21:38:35 +02:00
parent 588b52de14
commit 5f926ff5a2
5 changed files with 255 additions and 1 deletions
+8 -1
View File
@@ -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
+6
View File
@@ -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,
},
]
}
+53
View File
@@ -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
}
+54
View File
@@ -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],
}
+134
View File
@@ -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(())
}