From 8ac15ae0116b3a1af0556b49deae35ce126dd825 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:28:49 +0200 Subject: [PATCH] add cd5 extractor --- README.md | 4 ++ src/formats.rs | 6 +++ src/formats/cd5/include.rs | 78 ++++++++++++++++++++++++++++++++ src/formats/cd5/mod.rs | 91 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 src/formats/cd5/include.rs create mode 100644 src/formats/cd5/mod.rs diff --git a/README.md b/README.md index 8eb7b88..533d560 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ Options: **Used in:** Enterprise HP Printers **Notes:** None, all files should be supported +## CD5 +**Used in:** Some Samsung TV tuners, and possibly other Irdeto(?)-based tuners +**Notes:** Decryption is not supported. + ## EPK v1 **Used in:** LG TVs before ~2010 **Notes:** None, all files should be supported diff --git a/src/formats.rs b/src/formats.rs index 765a914..047d930 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -28,6 +28,7 @@ pub mod bdl; pub mod amlogic; pub mod sdboot; pub mod sdimage; +pub mod cd5; pub mod pup; @@ -214,6 +215,11 @@ pub fn get_registry() -> Vec { detector_func: crate::formats::mtk_bdp::is_mtk_bdp_file, extractor_func: crate::formats::mtk_bdp::extract_mtk_bdp, }, + Format { + name: "cd5", + detector_func: crate::formats::cd5::is_cd5_file, + extractor_func: crate::formats::cd5::extract_cd5, + }, ] } \ No newline at end of file diff --git a/src/formats/cd5/include.rs b/src/formats/cd5/include.rs new file mode 100644 index 0000000..8fff15c --- /dev/null +++ b/src/formats/cd5/include.rs @@ -0,0 +1,78 @@ +use binrw::BinRead; + +//all big endian + +#[derive(BinRead)] +pub struct DescriptorHeader { + pub magic: u8, + pub size: u16, +} + +#[derive(BinRead)] +pub struct DownloadHeader { + // preceeded by DescriptorHeader + pub manufacturer_code: u16, + pub hardware_version: u16, + _unk1: u16, + _unk2: u16, + pub variant: u16, + pub sub_variant: u16, + _unk_string_bytes: [u8; 8], + pub version: u16, // DSN/download sequence number + pub module_count: u16, + #[br(count=module_count)] pub module_entries: Vec, + _signature_size: u16, + #[br(count=_signature_size)] pub _signature: Vec, + _extra_data_size: u16, + #[br(count=_extra_data_size)] pub _extra_data: Vec, + _checksum: u16, //crc16 of header (not including magic and size) +} + +#[derive(BinRead)] +pub struct DownloadHeaderModuleEntry { + pub module_id: u16, + pub version: u16 //DSN +} + +#[derive(BinRead)] +pub struct ModuleDownloadHeader { + // preceeded by DescriptorHeader + pub module_id: u16, + flags: u8, + pub out_size: u32, + pub segment_size: u16, + pub segment_count: u16, + _checksum: u16, //crc16 of header (not including magic and size) +} +impl ModuleDownloadHeader { + pub fn is_encrypted(&self) -> bool { + (self.flags & (1 << 5)) != 0 + } +} + +#[derive(BinRead)] +pub struct DownloadSegment { + pub magic: u8, + pub module_id: u16, + pub data_size: u16, + #[br(count=data_size)] pub data: Vec, + _checksum: u16, //crc16 of module_id+size+(STORED)data +} + +#[derive(BinRead)] +pub struct InnerModuleHeader { + _module_id: u16, + pub header_size: u16, + pub data_size: u32, + _unk1: u8, + _version: u16, //dsn + _variant: u16, + _sub_variant: u16, + _version2: u16, + _unk2: [u8; 5], + _signature1_size: u16, + #[br(count=_signature1_size)] pub _signature1: Vec, + _signature2_size: u16, + #[br(count=_signature2_size)] pub _signature2: Vec, + _checksum: u32, //crc32 of header +} \ No newline at end of file diff --git a/src/formats/cd5/mod.rs b/src/formats/cd5/mod.rs new file mode 100644 index 0000000..e21f2c0 --- /dev/null +++ b/src/formats/cd5/mod.rs @@ -0,0 +1,91 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +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::*; + +pub fn is_cd5_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let hdr_magic = common::read_file(&file, 15, 8)?; + if hdr_magic == b"20 10001" { //not sure about it but fine for samsung and telestar + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let dwld_hdr_desc: DescriptorHeader = file.read_be()?; + if dwld_hdr_desc.magic != 0x11 { + return Err("Invalid download header magic!".into()); + } + let mut dwld_hdr_reader = Cursor::new(common::read_exact(&mut file, dwld_hdr_desc.size as usize)?); + let dwld_hdr: DownloadHeader = dwld_hdr_reader.read_be()?; + + // like Loader Data screen + println!("File info -\nManufacturer code: {}\nHardware Version: {}\nVersion(DSN): {}(0x{:02x})\nVariant/Sub-variant: 0x{:02x}/0x{:02x}\nModule count: {}", + dwld_hdr.manufacturer_code, dwld_hdr.hardware_version, dwld_hdr.version, dwld_hdr.version, dwld_hdr.variant, dwld_hdr.sub_variant, dwld_hdr.module_count); + + for (i, module) in dwld_hdr.module_entries.iter().enumerate() { + let mod_hdr_desc: DescriptorHeader = file.read_be()?; + if mod_hdr_desc.magic != 0x22 { + return Err("Invalid module download header magic!".into()); + } + let mut mod_hdr_reader = Cursor::new(common::read_exact(&mut file, mod_hdr_desc.size as usize)?); + let mod_hdr: ModuleDownloadHeader = mod_hdr_reader.read_be()?; + if mod_hdr.module_id != module.module_id { + return Err("Module id mismatch in download header and module header!".into()); + } + + println!("\n({}/{}) Module {}(0x{:02x}) - Version(DSN): {}(0x{:02x}), Size: {}, Segment size: {}, Segment count: {} {}", + i+1, dwld_hdr.module_count, mod_hdr.module_id, mod_hdr.module_id, module.version, module.version, mod_hdr.out_size, mod_hdr.segment_size, mod_hdr.segment_count, + if mod_hdr.is_encrypted() {"[ENCRYPTED]"} else {""}); + + let mut module_data: Vec = Vec::new(); + + for s_i in 0..mod_hdr.segment_count { + let mut segment: DownloadSegment = file.read_be()?; + if segment.magic != 0x33 { + return Err("Invalid segment magic!".into()); + } + if segment.module_id != mod_hdr.module_id { + return Err("Module id mismatch in segment and module header!".into()); + } + + println!(" Segment {}/{} - Size: {}", s_i+1, mod_hdr.segment_count, segment.data_size); + module_data.append(&mut segment.data); + } + + let out_data; + if mod_hdr.is_encrypted() { + println!("- Warning: data is encrypted, so cannot read inner header - saving ENCRYPTED data!"); + out_data = module_data; + } + else { + let mut mod_data_rdr = Cursor::new(module_data); + let inner_mod_hdr: InnerModuleHeader = mod_data_rdr.read_be()?; + println!("- Inner header size: {}, Data size: {}", inner_mod_hdr.header_size, inner_mod_hdr.data_size); + mod_data_rdr.seek(SeekFrom::Start(inner_mod_hdr.header_size as u64))?; + out_data = common::read_exact(&mut mod_data_rdr, inner_mod_hdr.data_size as usize)?; + } + + let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", mod_hdr.module_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(()) +} \ No newline at end of file