Added replace by name for PAK

This commit is contained in:
G2-Games 2024-07-06 21:30:05 -05:00
parent 84c02a1347
commit 8c9c33b670
3 changed files with 71 additions and 71 deletions

View file

@ -8,6 +8,8 @@ use std::{
/// A single file entry in a PAK file
#[derive(Debug, Clone)]
pub struct Entry {
pub(super) index: usize,
/// The location within the PAK file, this number is multiplied by the
/// block size
pub(super) offset: u32,
@ -34,6 +36,10 @@ impl Entry {
&self.name
}
pub fn id(&self) -> u32 {
self.id
}
/// Save an [`Entry`] as its underlying data to a file
pub fn save<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
let mut path = path.as_ref().to_path_buf();

View file

@ -38,11 +38,9 @@ pub struct Pak {
path: PathBuf,
header: Header,
pub unknown_pre_data: Vec<u32>,
unknown_pre_data: Vec<u32>,
unknown_post_header: Vec<u8>,
rebuild: bool, // TODO: Look into a better way to indicate this, or if it's needed at all
entries: Vec<Entry>,
}
@ -59,7 +57,7 @@ impl Pak {
Pak::decode(&mut file, path.as_ref().to_path_buf())
}
/// Decode a PAK file from a byte stream
/// Decode a PAK file from a byte stream.
pub fn decode<T: Seek + Read>(
input: &mut T,
path: PathBuf,
@ -68,7 +66,7 @@ impl Pak {
let mut input = BufReader::new(input);
// Read in all the header bytes
info!("READING: header");
debug!("READING: header");
let header = Header {
data_offset: input.read_u32::<LE>()?,
entry_count: input.read_u32::<LE>()?,
@ -81,8 +79,8 @@ impl Pak {
flags: PakFlags(input.read_u32::<LE>()?),
};
info!("{} entries detected", header.entry_count);
info!("Block size is {} bytes", header.block_size);
info!("Flag bits {:#032b}", header.flags().0);
debug!("Block size is {} bytes", header.block_size);
debug!("Flag bits {:#032b}", header.flags().0);
let first_offset = header.data_offset() / header.block_size();
@ -97,7 +95,7 @@ impl Pak {
unknown_pre_data.push(unknown);
}
info!("Pre-position bytes: {}", unknown_pre_data.len());
debug!("Pre-position bytes: {}", unknown_pre_data.len());
if input.stream_position()? == header.data_offset() as u64 {
log::error!("Header length exceeded first data block");
@ -106,7 +104,7 @@ impl Pak {
// Read all the offsets and lengths
// TODO: I think a flag controls this
info!("READING: offsets");
debug!("READING: offsets");
let mut offsets = Vec::new();
for _ in 0..header.entry_count() {
let offset = input.read_u32::<LE>().unwrap();
@ -120,7 +118,7 @@ impl Pak {
// Read all unknown_data1
let mut unknown_data1 = None;
if header.flags.has_unknown_data1() {
info!("READING: unknown_data1");
debug!("READING: unknown_data1");
unknown_data1 = Some(Vec::new());
let mut buf = [0u8; 12];
for _ in 0..header.entry_count() {
@ -133,7 +131,7 @@ impl Pak {
// Read all the file names
let mut file_names = None;
if header.flags.has_names() {
info!("READING: file_names");
debug!("READING: file_names");
let mut string_buf = Vec::new();
file_names = Some(Vec::new());
for _ in 0..header.entry_count() {
@ -151,7 +149,7 @@ impl Pak {
input.read_exact(&mut unknown_post_header)?;
// Read all entry data
info!("Creating entry list");
debug!("Creating entry list");
let mut entries: Vec<Entry> = Vec::new();
for (i, offset_info) in offsets.iter().enumerate().take(header.entry_count() as usize) {
debug!("Seeking to block {}", offset_info.offset);
@ -178,6 +176,7 @@ impl Pak {
// Build the entry from the data we now know
let entry = Entry {
index: i,
offset: offset_info.offset,
length: offset_info.length,
unknown1,
@ -187,7 +186,7 @@ impl Pak {
};
entries.push(entry);
}
info!("Entry list contains {} entries", entries.len());
debug!("Entry list contains {} entries", entries.len());
Ok(Pak {
header,
@ -195,10 +194,10 @@ impl Pak {
entries,
unknown_post_header,
path,
rebuild: false,
})
}
/// Encode a PAK file into a byte stream.
pub fn encode<T: Write + Seek>(
&self,
mut output: &mut T
@ -256,31 +255,70 @@ impl Pak {
Ok(())
}
/// Replace the data of an entry with some other bytes.
///
/// This function updates the offsets of all entries to fit within the
/// chunk size specified in the header.
pub fn replace(
&mut self,
index: usize,
replacement_bytes: &[u8]
replacement_bytes: &[u8],
) -> Result<(), PakError> {
let block_size = self.header().block_size();
let replaced_entry = if let Some(entry) = self.entries.get_mut(index) {
entry
let replaced_entry;
if let Some(entry) = self.entries.get_mut(index) {
replaced_entry = entry
} else {
log::error!("Entry {} not found!", index);
return Err(PakError::IndexError)
};
if let Some(name) = replaced_entry.name() {
info!("Replacing entry {}: {}", index, name);
} else {
info!("Replacing entry {}: {}", index, replaced_entry.id());
}
// Replace the entry data
replaced_entry.data = replacement_bytes.to_vec();
replaced_entry.length = replaced_entry.data.len() as u32;
let mut next_offset = replaced_entry.offset + replaced_entry.length.div_ceil(block_size);
// Get the offset of the next entry based on the current one
let mut next_offset =
replaced_entry.offset + replaced_entry.length.div_ceil(block_size);
// Update the position of all subsequent entries
let mut i = 0;
for entry in self.entries.iter_mut().skip(index + 1) {
entry.offset = next_offset;
next_offset = entry.offset + entry.length.div_ceil(block_size);
i += 1;
}
info!("Aligned {} subsequent entries", i);
Ok(())
}
/// Replace the data of an entry with some other bytes, indexing by name.
///
/// Read more in [`Pak::replace()`]
pub fn replace_by_name(
&mut self,
name: String,
replacement_bytes: &[u8],
) -> Result<(), PakError> {
let entry = self.get_entry_by_name(&name);
let index = if let Some(entry) = entry {
entry.index
} else {
return Err(PakError::IndexError)
};
self.replace(index, replacement_bytes)?;
Ok(())
}
@ -293,21 +331,18 @@ impl Pak {
&self.path
}
/// Get an individual entry from the PAK by its index
pub fn get_entry(&self, index: u32) -> Option<&Entry> {
self.entries.get(index as usize)
}
/// Get an individual entry from the PAK by its ID
pub fn get_entry_by_id(&self, id: u32) -> Option<&Entry> {
self.entries.get((id - self.header.id_start) as usize)
pub fn get_entry_by_id(&mut self, id: u32) -> Option<&mut Entry> {
self.entries
.get_mut((id - self.header.id_start) as usize)
}
pub fn get_entry_by_name(&self, name: &str) -> Option<&Entry> {
pub fn get_entry_by_name(&mut self, name: &str) -> Option<&mut Entry> {
self.entries
.iter()
.find(|e| e.name.as_ref()
.is_some_and(|n| n == &name))
.iter_mut()
.find(|e|
e.name.as_ref().is_some_and(|n| n == &name)
)
}
/// Get a list of all entries from the PAK

View file

@ -6,50 +6,9 @@ fn main() {
clog.filter(None, log::LevelFilter::Info);
clog.init();
/*
let paths = std::fs::read_dir(".")
.unwrap()
.filter_map(|res| res.ok())
.map(|dir_entry| dir_entry.path())
.filter_map(|path| {
if path.extension().map_or(false, |ext| ext.to_ascii_lowercase() == "pak") {
Some(path)
} else {
None
}
})
.collect::<Vec<_>>();
let mut pak_files = vec![];
for path in paths {
let pak = Pak::open(&path).unwrap();
pak_files.push(pak)
}
pak_files.sort_by_key(|x| x.header().flags().0 & 0xF);
for pak in pak_files {
println!(
"{:#032b} - {} - {:?}",
pak.header().flags().0,
pak.unknown_pre_data.len(),
pak.path(),
);
}
*/
let mut pak = Pak::open("MANUAL.PAK").unwrap();
//println!("{:#?}", pak.header());
//println!("{:#032b}", pak.header().flags().0);
/*
for (i, entry) in pak.entries().iter().enumerate() {
println!("{i:03}: {:06.2} kB - {}", entry.len() as f32 / 1_000.0, entry.name().as_ref().unwrap());
entry.save("./output/").unwrap();
}
*/
let rep_cz_data: Vec<u8> = std::fs::read("en_manual01_Linkto_2_6").unwrap();
let rep_cz_data: Vec<u8> = std::fs::read("en_manual01_Linkto_2_6.cz1").unwrap();
pak.replace(4, &rep_cz_data).unwrap();
let mut output = BufWriter::new(File::create("MANUAL-modified.PAK").unwrap());