nvt_timg: add support for more variants/types

This commit is contained in:
theubusu
2026-04-11 14:07:41 +02:00
parent 5cc00a773d
commit 558310c36e
3 changed files with 228 additions and 33 deletions
+2 -2
View File
@@ -129,8 +129,8 @@ Options:
**Notes:** None, all files should be supported. **Notes:** None, all files should be supported.
## Novatek TIMG ## Novatek TIMG
**Used in:** Newer Novatek-based TVs (Philips TitanOS, Hisense) **Used in:** Newer Novatek-based TVs (Philips(TPVision), Hisense, TCL...)
**Notes:** There is an older type of this format that is not yet supported, but for newer type all files should work. **Notes:** None, all files should be supported.
## 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
+180 -19
View File
@@ -1,38 +1,199 @@
use crate::utils::common; use crate::utils::common;
use binrw::BinRead; use binrw::BinRead;
#[derive(Debug, BinRead)] #[derive(PartialEq, Eq, Debug)]
pub struct TIMG { pub enum TimgVariant {
_magic_bytes: [u8; 4], //TIMG Old,
_unused1: u32, Old2,
pub data_size: u32, New,
_unused2: u32, }
_md5_checksum: [u8; 16],
_signature: [u8; 256], pub trait TIMG {
fn _magic_bytes(&self) -> Vec<u8>;
fn data_size(&self) -> usize;
fn _data_checksum(&self) -> [u8; 16]; //md5 of data_size after timg header
fn _signature(&self) -> [u8; 256];
} }
#[derive(Debug, BinRead)] #[derive(Debug, BinRead)]
pub struct PIMG { pub struct TIMG64 { //new
pub magic_bytes: [u8; 4], //PIMG _magic_bytes: [u8; 8], //TIMG/x00/x00/x00/x00
_unused1: u32, data_size: u64,
pub size: u32, _data_checksum: [u8; 16],
_unused2: u32, _signature: [u8; 256],
_md5_checksum: [u8; 16], }
impl TIMG for TIMG64 {
fn _magic_bytes(&self) -> Vec<u8> {
self._magic_bytes.to_vec()
}
fn data_size(&self) -> usize {
self.data_size as usize
}
fn _data_checksum(&self) -> [u8; 16] {
self._data_checksum
}
fn _signature(&self) -> [u8; 256] {
self._signature
}
}
#[derive(Debug, BinRead)]
pub struct TIMG32 {
_magic_bytes: [u8; 4], //TIMG
data_size: u32,
_data_checksum: [u8; 16],
_signature: [u8; 256],
}
impl TIMG for TIMG32 {
fn _magic_bytes(&self) -> Vec<u8> {
self._magic_bytes.to_vec()
}
fn data_size(&self) -> usize {
self.data_size as usize
}
fn _data_checksum(&self) -> [u8; 16] {
self._data_checksum
}
fn _signature(&self) -> [u8; 256] {
self._signature
}
}
#[derive(Debug, BinRead)]
pub struct TIMGOld2 {
_magic_bytes: [u8; 4], //TIMG
data_size: u32,
_data_checksum: [u8; 16],
_pad: u32,
_signature: [u8; 256],
}
impl TIMG for TIMGOld2 {
fn _magic_bytes(&self) -> Vec<u8> {
self._magic_bytes.to_vec()
}
fn data_size(&self) -> usize {
self.data_size as usize
}
fn _data_checksum(&self) -> [u8; 16] {
self._data_checksum
}
fn _signature(&self) -> [u8; 256] {
self._signature
}
}
pub trait PIMG {
fn magic_bytes(&self) -> Vec<u8>;
fn name(&self) -> String;
fn size(&self) -> usize;
fn _checksum(&self) -> [u8; 16]; //md5 of stored data
fn dest_dev(&self) -> String;
fn comp_type(&self) -> String;
fn comment(&self) -> String;
}
#[derive(Debug, BinRead)]
pub struct PIMG64 {
magic_bytes: [u8; 8], //PIMG\x00\x00\x00\x00
size: u64,
_checksum: [u8; 16],
name_bytes: [u8; 16], name_bytes: [u8; 16],
dest_dev_bytes: [u8; 64], dest_dev_bytes: [u8; 64],
comp_type_bytes: [u8; 16], comp_type_bytes: [u8; 16],
_unknown1: u32, _unknown1: u32,
_comment: [u8; 1024], comment_bytes: [u8; 1024],
_unknown2: u32, _unknown2: u32,
} }
impl PIMG { impl PIMG for PIMG64 {
pub fn name(&self) -> String { fn magic_bytes(&self) -> Vec<u8> {
self.magic_bytes.to_vec()
}
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes) common::string_from_bytes(&self.name_bytes)
} }
pub fn dest_dev(&self) -> String { fn size(&self) -> usize {
self.size as usize
}
fn _checksum(&self) -> [u8; 16] {
self._checksum
}
fn dest_dev(&self) -> String {
common::string_from_bytes(&self.dest_dev_bytes) common::string_from_bytes(&self.dest_dev_bytes)
} }
pub fn comp_type(&self) -> String { fn comp_type(&self) -> String {
common::string_from_bytes(&self.comp_type_bytes) common::string_from_bytes(&self.comp_type_bytes)
} }
fn comment(&self) -> String {
common::string_from_bytes(&self.comment_bytes)
}
}
#[derive(Debug, BinRead)]
pub struct PIMG32 {
magic_bytes: [u8; 4], //PIMG
size: u32,
_checksum: [u8; 16],
name_bytes: [u8; 16],
dest_dev_bytes: [u8; 32],
comp_type_bytes: [u8; 16],
_unknown1: u32,
comment_bytes: [u8; 1024],
_unknown2: u32,
}
impl PIMG for PIMG32 {
fn magic_bytes(&self) -> Vec<u8> {
self.magic_bytes.to_vec()
}
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn size(&self) -> usize {
self.size as usize
}
fn _checksum(&self) -> [u8; 16] {
self._checksum
}
fn dest_dev(&self) -> String {
common::string_from_bytes(&self.dest_dev_bytes)
}
fn comp_type(&self) -> String {
common::string_from_bytes(&self.comp_type_bytes)
}
fn comment(&self) -> String {
common::string_from_bytes(&self.comment_bytes)
}
}
#[derive(Debug, BinRead)]
pub struct PIMGOld2 {
magic_bytes: [u8; 4], //PIMG
size: u32,
_checksum: [u8; 16],
name_bytes: [u8; 16],
dest_dev_bytes: [u8; 24],
comp_type_bytes: [u8; 16],
_unknown1: u32,
}
impl PIMG for PIMGOld2 {
fn magic_bytes(&self) -> Vec<u8> {
self.magic_bytes.to_vec()
}
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn size(&self) -> usize {
self.size as usize
}
fn _checksum(&self) -> [u8; 16] {
self._checksum
}
fn dest_dev(&self) -> String {
common::string_from_bytes(&self.dest_dev_bytes)
}
fn comp_type(&self) -> String {
common::string_from_bytes(&self.comp_type_bytes)
}
fn comment(&self) -> String {
"".to_string() //yes (this variant has no comment)
}
} }
+46 -12
View File
@@ -12,35 +12,66 @@ use crate::utils::compression::{decompress_gzip};
use crate::utils::sparse::{unsparse_to_file}; use crate::utils::sparse::{unsparse_to_file};
use include::*; use include::*;
pub struct TimgContext {
variant: TimgVariant,
}
pub fn is_nvt_timg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> { pub fn is_nvt_timg_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 file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let header = common::read_file(&file, 0, 4)?; let header = common::read_file(&file, 0, 8)?;
if header == b"TIMG" { if header == b"TIMG\x00\x00\x00\x00" { //new variant checks magic as 64bit int (probably)
Ok(Some(Box::new(()))) Ok(Some(Box::new(TimgContext {variant: TimgVariant::New})))
} else if header.starts_with(b"TIMG") {
//check based on where the first PIMG appears, since Old2 header is 4 bytes bigger, it will appear later
let check = common::read_file(&file, 280, 8)?;
if &check[0..4] == b"PIMG" {
Ok(Some(Box::new(TimgContext {variant: TimgVariant::Old})))
}
else if &check[4..8] == b"PIMG" {
Ok(Some(Box::new(TimgContext {variant: TimgVariant::Old2})))
}
else {
Ok(None) //?
}
} else { } else {
Ok(None) Ok(None)
} }
} }
pub fn extract_nvt_timg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_nvt_timg(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")?;
let ctx = ctx.downcast::<TimgContext>().expect("Missing context");
let file_size = file.metadata()?.len(); let timg: Box<dyn TIMG> = match ctx.variant {
let timg: TIMG = file.read_le()?; TimgVariant::New => Box::new(file.read_le::<TIMG64>()?),
println!("File info:\nData size: {}", timg.data_size); TimgVariant::Old => Box::new(file.read_le::<TIMG32>()?),
TimgVariant::Old2 => Box::new(file.read_le::<TIMGOld2>()?),
};
println!("File info:\nVariant: {:?}\nData size: {}", ctx.variant, timg.data_size());
//position after header + data size
let end = file.stream_position()? + timg.data_size() as u64;
let mut pimg_i = 0; let mut pimg_i = 0;
while file.stream_position()? < file_size as u64 { while file.stream_position()? < end {
pimg_i += 1; pimg_i += 1;
let pimg: PIMG = file.read_le()?;
if &pimg.magic_bytes != b"PIMG" { let pimg: Box<dyn PIMG> = match ctx.variant {
TimgVariant::New => Box::new(file.read_le::<PIMG64>()?),
TimgVariant::Old => Box::new(file.read_le::<PIMG32>()?),
TimgVariant::Old2 => Box::new(file.read_le::<PIMGOld2>()?),
};
if !pimg.magic_bytes().starts_with(b"PIMG") {
return Err("Invalid PIMG magic!".into()); return Err("Invalid PIMG magic!".into());
} }
let data = common::read_exact(&mut file, pimg.size as usize)?; let data = common::read_exact(&mut file, pimg.size())?;
println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}", pimg_i, pimg.name(), pimg.size, pimg.dest_dev(), pimg.comp_type()); println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}, Comment: {}",
pimg_i, pimg.name(), pimg.size(), pimg.dest_dev(), pimg.comp_type(), pimg.comment());
let out_data; let out_data;
let output_path = Path::new(&app_ctx.output_dir).join(pimg.name() + ".bin"); let output_path = Path::new(&app_ctx.output_dir).join(pimg.name() + ".bin");
@@ -48,13 +79,16 @@ pub fn extract_nvt_timg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
if pimg.comp_type() == "gzip" && data.starts_with(b"\x1F\x8B") { //additionally check for gzip header, because sometimes its deceptive if pimg.comp_type() == "gzip" && data.starts_with(b"\x1F\x8B") { //additionally check for gzip header, because sometimes its deceptive
println!("- Decompressing gzip..."); println!("- Decompressing gzip...");
out_data = decompress_gzip(&data)?; out_data = decompress_gzip(&data)?;
} else if pimg.comp_type() == "none" || pimg.comp_type() == "" { } else if pimg.comp_type() == "none" || pimg.comp_type() == "" {
out_data = data; out_data = data;
} else if pimg.comp_type() == "sparse" { } else if pimg.comp_type() == "sparse" {
println!("- Unsparsing..."); println!("- Unsparsing...");
unsparse_to_file(&data, output_path)?; unsparse_to_file(&data, output_path)?;
println!("-- Saved file!"); println!("-- Saved file!");
continue continue
} else { } else {
println!("- Warning: unsupported compression type, saving stored data!"); println!("- Warning: unsupported compression type, saving stored data!");
out_data = data; out_data = data;