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.
## Novatek TIMG
**Used in:** Newer Novatek-based TVs (Philips TitanOS, Hisense)
**Notes:** There is an older type of this format that is not yet supported, but for newer type all files should work.
**Used in:** Newer Novatek-based TVs (Philips(TPVision), Hisense, TCL...)
**Notes:** None, all files should be supported.
## Panasonic Blu-Ray (PANA_DVD.FRM, PANA_ESD.FRM, PANAEUSB.FRM)
**Used in:** Panasonic Blu-Ray Players and Recorders
+180 -19
View File
@@ -1,38 +1,199 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(Debug, BinRead)]
pub struct TIMG {
_magic_bytes: [u8; 4], //TIMG
_unused1: u32,
pub data_size: u32,
_unused2: u32,
_md5_checksum: [u8; 16],
_signature: [u8; 256],
#[derive(PartialEq, Eq, Debug)]
pub enum TimgVariant {
Old,
Old2,
New,
}
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)]
pub struct PIMG {
pub magic_bytes: [u8; 4], //PIMG
_unused1: u32,
pub size: u32,
_unused2: u32,
_md5_checksum: [u8; 16],
pub struct TIMG64 { //new
_magic_bytes: [u8; 8], //TIMG/x00/x00/x00/x00
data_size: u64,
_data_checksum: [u8; 16],
_signature: [u8; 256],
}
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],
dest_dev_bytes: [u8; 64],
comp_type_bytes: [u8; 16],
_unknown1: u32,
_comment: [u8; 1024],
comment_bytes: [u8; 1024],
_unknown2: u32,
}
impl PIMG {
pub fn name(&self) -> String {
impl PIMG for PIMG64 {
fn magic_bytes(&self) -> Vec<u8> {
self.magic_bytes.to_vec()
}
fn name(&self) -> String {
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)
}
pub fn comp_type(&self) -> String {
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 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 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>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let header = common::read_file(&file, 0, 4)?;
if header == b"TIMG" {
Ok(Some(Box::new(())))
let header = common::read_file(&file, 0, 8)?;
if header == b"TIMG\x00\x00\x00\x00" { //new variant checks magic as 64bit int (probably)
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 {
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 ctx = ctx.downcast::<TimgContext>().expect("Missing context");
let file_size = file.metadata()?.len();
let timg: TIMG = file.read_le()?;
println!("File info:\nData size: {}", timg.data_size);
let timg: Box<dyn TIMG> = match ctx.variant {
TimgVariant::New => Box::new(file.read_le::<TIMG64>()?),
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;
while file.stream_position()? < file_size as u64 {
while file.stream_position()? < end {
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());
}
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 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
println!("- Decompressing gzip...");
out_data = decompress_gzip(&data)?;
} else if pimg.comp_type() == "none" || pimg.comp_type() == "" {
out_data = data;
} else if pimg.comp_type() == "sparse" {
println!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
continue
} else {
println!("- Warning: unsupported compression type, saving stored data!");
out_data = data;