diff --git a/src/scripts/qlie/archive/pack/encryption.rs b/src/scripts/qlie/archive/pack/encryption.rs index 1ca2c14..bc64235 100644 --- a/src/scripts/qlie/archive/pack/encryption.rs +++ b/src/scripts/qlie/archive/pack/encryption.rs @@ -33,6 +33,9 @@ pub trait Encryption: std::fmt::Debug { stream: Box, entry: &QlieEntry, ) -> Result>; + fn index_has_hash(&self) -> bool { + true + } } pub fn create_encryption( @@ -43,6 +46,8 @@ pub fn create_encryption( match (major, minor) { (3, 1) => Ok(Box::new(Encryption31::new())), (3, 0) => Ok(Box::new(Encryption30::new(game_key))), + (2, 0) => Ok(Box::new(Encryption20::new())), + (1, 0) => Ok(Box::new(Encryption10::new())), _ => Err(anyhow::anyhow!( "Unsupported encryption version: {}.{}", major, @@ -1249,6 +1254,124 @@ impl<'a> Read for Encryption30Decrypt<'a> { } } +#[derive(Debug)] +pub struct Encryption10 {} + +impl Encryption10 { + pub fn new() -> Self { + Self {} + } +} + +impl Encryption for Encryption10 { + fn decrypt_name(&self, name: &mut [u8], _hash: i32, encoding: Encoding) -> Result { + const KEY: u32 = 0xC4 ^ 0x3E; + for (i, b) in name.iter_mut().enumerate() { + let i = (i + 1) as u32; + *b ^= ((KEY ^ i).wrapping_add(i)) as u8; + } + decode_to_string(encoding, name, true) + } + + fn decrypt_entry<'a>( + &self, + stream: Box, + entry: &QlieEntry, + ) -> Result> { + if entry.is_encrypted == 0 { + return Ok(Box::new(stream)); + } + Ok(Box::new(Encryption10Decrypt::new(stream, 0))) + } + + fn index_has_hash(&self) -> bool { + false + } +} + +#[derive(Debug)] +pub struct Encryption20 { + index_has_hash: bool, +} + +impl Encryption20 { + pub fn new() -> Self { + Self { + index_has_hash: true, + } + } + + pub fn new_no_hash() -> Self { + Self { + index_has_hash: false, + } + } +} + +impl Encryption for Encryption20 { + fn decrypt_name(&self, name: &mut [u8], _hash: i32, encoding: Encoding) -> Result { + let key = (0xC4u32 ^ 0x3E).wrapping_add(name.len() as u32); + for (i, b) in name.iter_mut().enumerate() { + let i = (i + 1) as u32; + *b ^= ((key ^ i).wrapping_add(i)) as u8; + } + decode_to_string(encoding, name, true) + } + + fn decrypt_entry<'a>( + &self, + stream: Box, + entry: &QlieEntry, + ) -> Result> { + if entry.is_encrypted == 0 { + return Ok(Box::new(stream)); + } + Ok(Box::new(Decrypter::new(stream, 0, entry.size))) + } + + fn index_has_hash(&self) -> bool { + self.index_has_hash + } +} + +#[derive(Debug)] +struct Encryption10Decrypt<'a> { + stream: Box, + v5: u64, + v9: u64, +} + +impl<'a> Encryption10Decrypt<'a> { + pub fn new(stream: Box, key: u32) -> AlignedReader<8, Self> { + const C1: u64 = 0xA73C5F9D; + const C3: u64 = 0xFEC9753E; + const V5_INIT: u64 = mmx_punpckldq2(C1); + let v9 = mmx_punpckldq2((key as u64) ^ C3); + AlignedReader::new(Self { + stream, + v5: V5_INIT, + v9, + }) + } +} + +impl<'a> Read for Encryption10Decrypt<'a> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let readed = self.stream.read_most(buf)?; + let round = readed / 8; + let mut writer = MemWriterRef::new(buf); + const C2: u64 = 0xCE24F523; + const V7: u64 = mmx_punpckldq2(C2); + for _ in 0..round { + let d = writer.peek_u64()?; + self.v5 = mmx_p_add_d(self.v5, V7) ^ self.v9; + self.v9 = d ^ self.v5; + writer.write_u64(self.v9)?; + } + Ok(readed) + } +} + #[test] fn test_compress_decompress() -> Result<()> { let data = b"The quick brown fox jumps over the lazy dog.".repeat(100); diff --git a/src/scripts/qlie/archive/pack/mod.rs b/src/scripts/qlie/archive/pack/mod.rs index a7a6c92..8bd3927 100644 --- a/src/scripts/qlie/archive/pack/mod.rs +++ b/src/scripts/qlie/archive/pack/mod.rs @@ -178,7 +178,7 @@ impl QliePackArchive { if major == 3 && minor == 0 { game_key = encryption::find_key_data(filename)?; } - let encryption = encryption::create_encryption(major, minor, game_key)?; + let mut encryption = encryption::create_encryption(major, minor, game_key)?; // Read key let mut key = 0; let mut qkey = None; @@ -201,37 +201,31 @@ impl QliePackArchive { qkey = Some(qk); } // Read entries - let mut entries = Vec::new(); - reader.seek(SeekFrom::Start(header.index_offset))?; - for _ in 0..header.file_count { - let name_length = reader.read_u16()?; - let raw_name_length = if encryption.is_unicode() { - name_length as usize * 2 - } else { - name_length as usize - }; - let mut raw_name = reader.read_exact_vec(raw_name_length)?; - let name = encryption.decrypt_name(&mut raw_name, key as i32, archive_encoding)?; - let offset = reader.read_u64()?; - let size = reader.read_u32()?; - let unpacked_size = reader.read_u32()?; - let is_packed = reader.read_u32()?; - let is_encrypted = reader.read_u32()?; - let hash = reader.read_u32()?; - let entry = QlieEntry { - raw_name, - name, - offset, - size, - unpacked_size, - is_packed, - is_encrypted, - hash, - key, - common_key: None, - }; - entries.push(entry); - } + let mut entries = if major >= 2 { + Self::read_entries(&mut reader, key, &header, archive_encoding, &encryption)? + } else { + let possible_encs: [Box; 3] = [ + Box::new(encryption::Encryption10::new()), + Box::new(encryption::Encryption20::new_no_hash()), + Box::new(encryption::Encryption20::new()), + ]; + let mut t = None; + for enc in possible_encs { + match Self::read_entries(&mut reader, key, &header, archive_encoding, &enc) { + Ok(entries) => { + encryption = enc; + t = Some(entries); + break; + } + Err(_) => continue, + } + } + t.ok_or_else(|| { + anyhow::anyhow!( + "Failed to read Qlie Pack Archive entries with any encryption method" + ) + })? + }; let mut common_key = None; if major >= 3 { common_key = encryption::find_game_key(filename)?; @@ -261,6 +255,48 @@ impl QliePackArchive { common_key, }) } + + fn read_entries( + reader: &mut T, + key: u32, + header: &QlieHeader, + archive_encoding: Encoding, + encryption: &Box, + ) -> Result> { + let mut entries = Vec::new(); + reader.seek(SeekFrom::Start(header.index_offset))?; + let has_hash = encryption.index_has_hash(); + for _ in 0..header.file_count { + let name_length = reader.read_u16()?; + let raw_name_length = if encryption.is_unicode() { + name_length as usize * 2 + } else { + name_length as usize + }; + let mut raw_name = reader.read_exact_vec(raw_name_length)?; + let name = encryption.decrypt_name(&mut raw_name, key as i32, archive_encoding)?; + let offset = reader.read_u64()?; + let size = reader.read_u32()?; + let unpacked_size = reader.read_u32()?; + let is_packed = reader.read_u32()?; + let is_encrypted = reader.read_u32()?; + let hash = if has_hash { reader.read_u32()? } else { 0 }; + let entry = QlieEntry { + raw_name, + name, + offset, + size, + unpacked_size, + is_packed, + is_encrypted, + hash, + key, + common_key: None, + }; + entries.push(entry); + } + Ok(entries) + } } impl Script for QliePackArchive {