diff --git a/luca_pak/src/entry.rs b/luca_pak/src/entry.rs index 0c998d5..d9752b2 100644 --- a/luca_pak/src/entry.rs +++ b/luca_pak/src/entry.rs @@ -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>(&self, path: &P) -> Result<(), Box> { let mut path = path.as_ref().to_path_buf(); diff --git a/luca_pak/src/lib.rs b/luca_pak/src/lib.rs index 3a98d97..001e1b6 100644 --- a/luca_pak/src/lib.rs +++ b/luca_pak/src/lib.rs @@ -38,11 +38,9 @@ pub struct Pak { path: PathBuf, header: Header, - pub unknown_pre_data: Vec, + unknown_pre_data: Vec, unknown_post_header: Vec, - rebuild: bool, // TODO: Look into a better way to indicate this, or if it's needed at all - entries: Vec, } @@ -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( 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::()?, entry_count: input.read_u32::()?, @@ -81,8 +79,8 @@ impl Pak { flags: PakFlags(input.read_u32::()?), }; 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::().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 = 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( &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 diff --git a/luca_pak/src/main.rs b/luca_pak/src/main.rs index ff26529..dfae568 100644 --- a/luca_pak/src/main.rs +++ b/luca_pak/src/main.rs @@ -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::>(); - - 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 = std::fs::read("en_manual01_Linkto_2_6").unwrap(); + let rep_cz_data: Vec = 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());