From f8c98cc321611be370ae30d81addda81acc90887 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:03:52 +0100 Subject: [PATCH] pana_dvd: add option to split MAIN module --- README.md | 2 + src/formats/pana_dvd/mod.rs | 17 ++++--- src/formats/pana_dvd/util.rs | 87 ++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/formats/pana_dvd/util.rs diff --git a/README.md b/README.md index 789907d..5bc6c44 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ Options: ## Panasonic Blu-Ray (PANA_DVD.FRM, PANA_ESD.FRM, PANAEUSB.FRM) **Used in:** Panasonic Blu-Ray Players and Recorders **Notes:** **Depends on keys** - see keys.rs (Included keys should work for 99% of players released in and before 2014, and some released in 2018), Note that there is currently an issue with MAIN in some very ancient files not extracting correctly. +**Options:** +`pana_dvd:split_main` - Automatically split the MAIN module into seperate partitions. ## Philips UPG (Autorun.upg, 2SWU3TXV) **Used in:** Philips pre-TPVision TVs 200?-2013 diff --git a/src/formats/pana_dvd/mod.rs b/src/formats/pana_dvd/mod.rs index 541e92d..4d3db46 100644 --- a/src/formats/pana_dvd/mod.rs +++ b/src/formats/pana_dvd/mod.rs @@ -1,6 +1,7 @@ mod include; mod pana_dvd_crypto; mod lzss; +mod util; use std::any::Any; use crate::AppContext; @@ -16,6 +17,7 @@ use crate::utils::compression::{decompress_gzip}; use pana_dvd_crypto::{decrypt_data}; use lzss::{decompress_lzss}; use include::*; +use util::split_main_file; pub struct PanaDvdContext { matching_key: [u8; 8], @@ -96,24 +98,25 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box) -> Result<(), B if file_entries.len() == 1 { //only one file, standard extraction println!("File contains no extra sub-files...\n"); - extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, &app_ctx.output_dir)?; + extract_file(app_ctx, &mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, &app_ctx.output_dir)?; } else { println!("File contains {} sub-files...", file_entries.len()); for (i, file_entry ) in file_entries.iter().enumerate() { println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset); - extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &app_ctx.output_dir.join(format!("file_{}", i + 1)))?; + extract_file(app_ctx, &mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &app_ctx.output_dir.join(format!("file_{}", i + 1)))?; } } Ok(()) } -fn extract_file(file_reader: &mut Cursor>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &PathBuf) -> Result<(), Box> { +fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &PathBuf) -> Result<(), Box> { file_reader.seek(SeekFrom::Start(offset + base_offset))?; let enc_header = common::read_exact(file_reader, MAX_HEADER_SIZE)?; let mut hdr_reader = Cursor::new(decrypt_data(&enc_header, &key)); let mut modules: Vec = Vec::new(); + let split_main = app_ctx.options.iter().any(|e| e == "pana_dvd:split_main"); for i in 0..100 { let mut entry: ModuleEntry = hdr_reader.read_le()?; @@ -146,7 +149,11 @@ fn extract_file(file_reader: &mut Cursor>, offset: u64, base_offset: u64 if module.name() == "MAIN" { println!("- Extracting MAIN..."); - extract_main(file_reader, key, output_path)?; + extract_main(file_reader, key, &output_path)?; + if split_main { + println!("\n- Splitting MAIN..."); + split_main_file(&output_path, output_folder)?; + } continue } @@ -169,7 +176,7 @@ fn extract_file(file_reader: &mut Cursor>, offset: u64, base_offset: u64 Ok(()) } -fn extract_main(file_reader: &mut Cursor>, key: [u8; 8], output_path: PathBuf) -> Result<(), Box> { +fn extract_main(file_reader: &mut Cursor>, key: [u8; 8], output_path: &PathBuf) -> Result<(), Box> { let main_list_hdr: MainListHeader = file_reader.read_le()?; if main_list_hdr.entry_count() > 200 { println!("Unsupported MAIN data, skipping!"); diff --git a/src/formats/pana_dvd/util.rs b/src/formats/pana_dvd/util.rs new file mode 100644 index 0000000..15bb910 --- /dev/null +++ b/src/formats/pana_dvd/util.rs @@ -0,0 +1,87 @@ +use crate::utils::common; +use std::{fs::{self, File, OpenOptions}, io::{Read, Write}, path::{Path, PathBuf}}; + +pub fn split_main_file(path: &PathBuf, out_path: &PathBuf) -> Result<(), Box> { + let mut file = File::open(path)?; + let mut file_size = file.metadata()?.len(); + let output_folder = Path::new(&out_path).join("MAIN"); + + let mut args_bytes = common::read_file(&mut file, 0, 0x1000)?; + let mut has_swup_addon = false; + if args_bytes.starts_with(b"SWUP_ADDON") { + has_swup_addon = true; + let addon_offset = u32::from_le_bytes(args_bytes[12..16].try_into()?); + file_size = addon_offset as u64; + args_bytes = common::read_file(&mut file, 16, 0x1000)?; + } + let args = common::string_from_bytes(&args_bytes); + + let mut root: Option = None; + let mut parts = Vec::<(String, u64)>::new(); + + //parse parts + for line in args.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + + //get the root partition, strip /dev/ + if let Some(rest) = line.strip_prefix("root=") { + let dev = rest.trim(); + if let Some(name) = dev.strip_prefix("/dev/") { + root = Some(name.to_string()); + } + } + + //get fmaX=XXXXXk partitions + if let Some((name, size_part)) = line.split_once('=') { + if name.starts_with("fma") { + let size_str = size_part.split(':').next().unwrap().trim(); + + if let Some(num) = size_str.strip_suffix('k') { + let kb: u64 = num.parse().map_err(|_| format!("Bad size: {}", size_str))?; + parts.push((name.to_string(), kb * 1024)); + } + } + } + } + + let root = root.ok_or("Failed to get root partition!")?; + println!("Root - {}", root); + let root_index = parts.iter().position(|(n, _s)| n == &root).ok_or("Root partition not found in partition list!")?; + + let mut tsize: u64 = 0; + + //read parts from file + let start_index = root_index - 1; // root is always the second partition in MAIN, so we start from the previous one. + for (part_name, part_size) in parts.iter().skip(start_index) { + if tsize >= file_size { + break + } + + println!("- {} - Size: {}", part_name, part_size); + tsize += part_size; + + let data = common::read_exact(&mut file, *part_size as usize)?; + + let output_path = Path::new(&output_folder).join(format!("{}.bin", part_name)); + fs::create_dir_all(&output_folder)?; + + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + out_file.write_all(&data)?; + } + + //read optional swup addon + if has_swup_addon { + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + println!("- SWUP_ADDON - Size: {}", data.len()); + + let output_path = Path::new(&output_folder).join("SWUP_ADDON.bin"); + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + out_file.write_all(&data)?; + } + + Ok(()) +} \ No newline at end of file