diff --git a/README.md b/README.md index 20cf32d..8eb7b88 100644 --- a/README.md +++ b/README.md @@ -162,13 +162,23 @@ Options: **Notes:** **Depends on keys** - see keys.rs **Thanks to:** https://github.com/george-hopkins/samygo-patcher +## SDBoot +**Used in:** Panasonic TVs SD boot +**Notes:** There is only one known sample, so support may vary. +**Base:** https://github.com/theubusu/sddl_dec + ## SDDL.SEC **Used in:** Panasonic TVs -**Notes:** Pre-2011 files are not supported. +**Notes:** None, all files should be supported. **Options:** `sddl_sec:save_extra` - Save SDIT.FDI and .TXT files that are not extracted by default. `sddl_sec:split_peaks` - Split PEAKS module into partitions (only on older files). This will also automatically decompress compressed partitions. -`sddl_sec:no_decomp_peaks` - Do not automatically decompress partitions when splitting PEAKS with above option. +`sddl_sec:no_decomp_peaks` - Do not automatically decompress partitions when splitting PEAKS with above option. +**Base:** https://github.com/theubusu/sddl_dec + +## SDImage (SDImage.bin) +**Used in:** Some 2010 USA Panasonic TVs +**Notes:** Decryption is not yet supported. ## SLP **Used in:** Samsung Tizen-based NX series cameras diff --git a/src/formats.rs b/src/formats.rs index 3120a78..765a914 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -27,6 +27,7 @@ pub mod android_ota_payload; pub mod bdl; pub mod amlogic; pub mod sdboot; +pub mod sdimage; pub mod pup; @@ -78,6 +79,11 @@ pub fn get_registry() -> Vec { detector_func: crate::formats::sdboot::is_sdboot_file, extractor_func: crate::formats::sdboot::extract_sdboot, }, + Format { + name: "sdimage", + detector_func: crate::formats::sdimage::is_sdimage_file, + extractor_func: crate::formats::sdimage::extract_sdimage, + }, Format { name: "novatek", detector_func: crate::formats::novatek::is_novatek_file, diff --git a/src/formats/sdimage/include.rs b/src/formats/sdimage/include.rs new file mode 100644 index 0000000..bff1654 --- /dev/null +++ b/src/formats/sdimage/include.rs @@ -0,0 +1,33 @@ +use binrw::BinRead; +use crate::utils::common; + +#[derive(BinRead)] +pub struct EntryHeader { + target_name_bytes: [u8; 4], + pub target_id: u32, + pub model_id: u32, + pub version: [u8; 4], + flags: u32, + pub size1: u32, + pub _size2: u32, + _info_size: u32, + _unused: u32, + _sha1: [u8; 20], + #[br(count = _info_size)] info_bytes: Vec, +} +impl EntryHeader { + pub fn target_name(&self) -> String { + let s = common::string_from_bytes(&self.target_name_bytes); + s.chars().rev().collect() + } + pub fn info(&self) -> String { + common::string_from_bytes(&self.info_bytes) + } + + pub fn is_encrypted(&self) -> bool { + (self.flags & (1 << 0)) != 0 + } + pub fn is_empty(&self) -> bool { + (self.flags & (1 << 3)) != 0 + } +} \ No newline at end of file diff --git a/src/formats/sdimage/mod.rs b/src/formats/sdimage/mod.rs new file mode 100644 index 0000000..4c789e2 --- /dev/null +++ b/src/formats/sdimage/mod.rs @@ -0,0 +1,57 @@ +mod include; +use std::any::Any; +use std::fs::{self, OpenOptions}; +use std::io::{Seek, Write}; +use std::path::Path; +use crate::AppContext; +use binrw::BinReaderExt; + +use include::*; +use crate::utils::common; + +pub fn is_sdimage_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 8)?; + if header == b"PFUS01US" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_sdimage(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + file.seek(std::io::SeekFrom::Start(16))?; + + let mut e_i = 0; + while file.stream_position()? < file.metadata()?.len() as u64 { + let entry: EntryHeader = file.read_le()?; + println!("\n#{} - {} ({}), Size: {}, Version: {}.{}.{}.{}, Model ID: {}, Info: {}{} {}", + e_i+1, entry.target_name(), entry.target_id, entry.size1, entry.version[3], entry.version[2], entry.version[1], entry.version[0], entry.model_id, entry.info(), + if entry.is_encrypted() {" [ENCRYPTED]"} else {""}, if entry.is_empty() {"[EMPTY]"} else {""}); + + if !entry.is_empty() { + let info = entry.info(); + let filename = info.split("FN=\"").nth(1).and_then(|s| s.split('"').next()).unwrap(); + println!("- Filename: {}", filename); + + let data = common::read_exact(&mut file, entry.size1 as usize)?; + + fs::create_dir_all(&app_ctx.output_dir)?; + let out_folder = Path::new(&app_ctx.output_dir).join(entry.target_name()); + fs::create_dir_all(&out_folder)?; + let output_path = out_folder.join(filename); + + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + out_file.write_all(&data)?; + + println!("-- Saved file!"); + } + + e_i += 1; + } + + Ok(()) +} \ No newline at end of file