add cd5 extractor

This commit is contained in:
theubusu
2026-04-09 16:28:49 +02:00
parent 67a289ba15
commit 8ac15ae011
4 changed files with 179 additions and 0 deletions
+4
View File
@@ -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
+6
View File
@@ -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<Format> {
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,
},
]
}
+78
View File
@@ -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<DownloadHeaderModuleEntry>,
_signature_size: u16,
#[br(count=_signature_size)] pub _signature: Vec<u8>,
_extra_data_size: u16,
#[br(count=_extra_data_size)] pub _extra_data: Vec<u8>,
_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<u8>,
_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<u8>,
_signature2_size: u16,
#[br(count=_signature2_size)] pub _signature2: Vec<u8>,
_checksum: u32, //crc32 of header
}
+91
View File
@@ -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<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
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<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
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<u8> = 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(())
}