From c9abc25336d0fc017b4f6b490e5c4159f5881e71 Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Fri, 19 Apr 2024 11:31:59 -0500 Subject: [PATCH] Fix empty intermediate directories being omitted from full directory index which can cause issues in some cases --- repak/src/pak.rs | 58 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/repak/src/pak.rs b/repak/src/pak.rs index a4109af..88b73a0 100644 --- a/repak/src/pak.rs +++ b/repak/src/pak.rs @@ -615,6 +615,19 @@ fn fnv64(data: &[u8], offset: u64) -> u64 { hash } +fn split_path_child(path: &str) -> Option<(&str, &str)> { + if path == "/" || path.is_empty() { + None + } else { + let path = path.strip_suffix('/').unwrap_or(path); + let i = path.rfind('/').map(|i| i + 1); + match i { + Some(i) => Some(path.split_at(i)), + None => Some(("/", path)), + } + } +} + fn generate_full_directory_index( writer: &mut W, entries: &BTreeMap, @@ -622,24 +635,21 @@ fn generate_full_directory_index( ) -> Result<(), super::Error> { let mut fdi = BTreeMap::new(); for (path, offset) in entries.keys().zip(offsets) { - let (directory, filename) = { - let i = path.rfind('/').map(|i| i + 1); // we want to include the slash on the directory - match i { - Some(i) => { - let (l, r) = path.split_at(i); - (l.to_owned(), r.to_owned()) - } - None => ("/".to_owned(), path.to_owned()), - } - }; + let mut p = path.as_str(); + while let Some((parent, _)) = split_path_child(p) { + p = parent; + fdi.entry(p).or_default(); + } + + let (directory, filename) = split_path_child(path).expect("none root path"); fdi.entry(directory) - .and_modify(|d: &mut BTreeMap| { - d.insert(filename.clone(), *offset); + .and_modify(|d: &mut BTreeMap<&str, u32>| { + d.insert(filename, *offset); }) .or_insert_with(|| { let mut files_and_offsets = BTreeMap::new(); - files_and_offsets.insert(filename.clone(), *offset); + files_and_offsets.insert(filename, *offset); files_and_offsets }); } @@ -672,3 +682,25 @@ fn encrypt(key: aes::Aes256, bytes: &mut [u8]) { key.encrypt_block(aes::Block::from_mut_slice(chunk)) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_split_path_child() { + assert_eq!( + split_path_child("a/really/long/path"), + Some(("a/really/long/", "path")) + ); + assert_eq!( + split_path_child("a/really/long/"), + Some(("a/really/", "long")) + ); + assert_eq!(split_path_child("a"), Some(("/", "a"))); + assert_eq!(split_path_child("a//b"), Some(("a//", "b"))); + assert_eq!(split_path_child("a//"), Some(("a/", ""))); + assert_eq!(split_path_child("/"), None); + assert_eq!(split_path_child(""), None); + } +}