Add PUP extractor (PS4)

This commit is contained in:
theubusu
2025-10-19 22:12:21 +02:00
parent 9631349ac8
commit ed2fd5776a
3 changed files with 188 additions and 0 deletions
+2
View File
@@ -6,6 +6,8 @@ pub mod sddl_sec;
pub mod novatek; pub mod novatek;
pub mod ruf; pub mod ruf;
pub mod pup;
pub mod msd; pub mod msd;
pub mod msd10; pub mod msd10;
pub mod msd11; pub mod msd11;
+182
View File
@@ -0,0 +1,182 @@
use std::path::{Path};
use std::fs::{self, File, OpenOptions};
use std::io;
use binrw::{BinRead, BinReaderExt};
use flate2::read::ZlibDecoder;
use std::io::{Write, Read, Seek, SeekFrom};
use crate::common;
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>,
_unk1: u32,
_unk2: u16,
_flags: u8,
_unk3: u8,
_header_size: u16,
_hash_size: u16,
file_size: u64,
entry_count: u16,
_hash_count: u16,
_unk4: u32,
}
#[derive(BinRead, Clone)]
struct Entry {
flags: u32,
_unk1: u32,
offset: u64,
compressed_size: u64,
uncompressed_size: u64,
}
impl Entry {
fn id(&self) -> u32 {
self.flags >> 20
}
fn is_compressed(&self) -> bool {
(self.flags & 8) != 0
}
fn is_blocked(&self) -> bool {
(self.flags & 0x800) != 0
}
fn is_block_table(&self) -> bool {
(self.flags & 1) != 0
}
}
#[derive(BinRead, Clone)]
struct BlockEntry {
offset: u32,
size: u32,
}
pub fn is_pup_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
if header == b"\x4F\x15\x3D\x1D" {
true
} else {
false
}
}
fn decompress_zlib(data: &[u8]) -> io::Result<Vec<u8>> {
let mut decoder = ZlibDecoder::new(data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
Ok(decompressed)
}
pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let header: Header = file.read_le()?;
println!("\nFile info:\nFile size: {}\nEntry count: {}",
header.file_size, header.entry_count);
let mut entries: Vec<Entry> = Vec::new();
let mut block_tables: Vec<Entry> = Vec::new();
for _i in 0..header.entry_count {
let entry: Entry = file.read_le()?;
if entry.is_block_table() {
block_tables.push(entry.clone())
}
entries.push(entry);
}
let mut e_i = 0;
for entry in &entries {
println!("\n{}/{}: ID: {} Offset: {}, Compressed Size: {}, Uncompressed Size: {}\nCompressed: {}, Blocked: {}, Block table: {}",
e_i + 1, entries.len(), entry.id(), entry.offset, entry.compressed_size, entry.uncompressed_size, entry.is_compressed(), entry.is_blocked(), entry.is_block_table());
if !entry.is_block_table () {
if entry.is_blocked() && entry.is_compressed() {
let block_size = 2_u32.pow(((entry.flags & 0xF000) >> 12) + 12);
let block_count = (block_size + entry.uncompressed_size as u32 - 1) / block_size;
let mut my_block_table: Option<Entry> = None;
println!("Block size: {}, Block count: {}", block_size, block_count);
for block_table in &block_tables {
if block_table.id() == e_i {
my_block_table = Some(block_table.clone());
println!("Found block table: Offset: {}, Size: {}", block_table.offset, block_table.compressed_size);
break
}
}
if my_block_table.is_none() {
println!("Failed to find block table!");
continue
}
let initial_offset = my_block_table.as_ref().unwrap().offset + (32 * block_count as u64) + (8 * block_count as u64);
file.seek(SeekFrom::Start(my_block_table.as_ref().unwrap().offset + 32 * block_count as u64))?;
for i in 0..block_count {
let block: BlockEntry = file.read_le()?;
let current_pos = file.stream_position()?;
let padding = block.size & 0xF;
let data_size = block.size - padding;
let compressed = if data_size == block_size {false} else {true};
println!("Block {}/{}: Offset: {}, Data Size: {}, Padding: {}, Compressed: {}", i + 1, block_count, block.offset, data_size, padding, compressed);
file.seek(std::io::SeekFrom::Start(initial_offset + block.offset as u64))?;
let out_data;
let data = common::read_exact(&mut file, data_size as usize)?;
if compressed {
println!("- Decompressing...");
out_data = decompress_zlib(&data)?;
} else {
out_data = data;
}
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.id()));
fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new()
.append(true)
.create(true)
.open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved!");
file.seek(std::io::SeekFrom::Start(current_pos))?;
}
} else {
let data = common::read_file(&file, entry.offset, entry.compressed_size as usize)?;
let out_data;
if entry.is_compressed() {
println!("- Decompressing...");
out_data = decompress_zlib(&data)?;
} else {
out_data = data;
}
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.id()));
fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new()
.write(true)
.create(true)
.open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
}
} else {
println!("- Skipping block table..")
}
e_i += 1;
}
println!("\nExtraction finished!");
Ok(())
}
+4
View File
@@ -94,6 +94,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Mstar upgrade file detected!"); println!("Mstar upgrade file detected!");
formats::mstar::extract_mstar(&file, &output_path)?; formats::mstar::extract_mstar(&file, &output_path)?;
} }
else if formats::pup::is_pup_file(&file) {
println!("PUP file detected!");
formats::pup::extract_pup(&file, &output_path)?;
}
else if formats::mtk_pkg::is_mtk_pkg_file(&file) { else if formats::mtk_pkg::is_mtk_pkg_file(&file) {
println!("MTK Pkg file detected!"); println!("MTK Pkg file detected!");
formats::mtk_pkg::extract_mtk_pkg(&file, &output_path)?; formats::mtk_pkg::extract_mtk_pkg(&file, &output_path)?;