mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 04:48:54 +08:00
Add support to unpack qlie pack ver 1.0/2.0
This commit is contained in:
@@ -33,6 +33,9 @@ pub trait Encryption: std::fmt::Debug {
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
entry: &QlieEntry,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>>;
|
||||
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<String> {
|
||||
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<dyn ReadSeek + 'a>,
|
||||
entry: &QlieEntry,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>> {
|
||||
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<String> {
|
||||
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<dyn ReadSeek + 'a>,
|
||||
entry: &QlieEntry,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>> {
|
||||
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<dyn ReadSeek + 'a>,
|
||||
v5: u64,
|
||||
v9: u64,
|
||||
}
|
||||
|
||||
impl<'a> Encryption10Decrypt<'a> {
|
||||
pub fn new(stream: Box<dyn ReadSeek + 'a>, 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<usize> {
|
||||
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);
|
||||
|
||||
@@ -178,7 +178,7 @@ impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
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<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
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<dyn Encryption>; 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<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
common_key,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_entries(
|
||||
reader: &mut T,
|
||||
key: u32,
|
||||
header: &QlieHeader,
|
||||
archive_encoding: Encoding,
|
||||
encryption: &Box<dyn Encryption>,
|
||||
) -> Result<Vec<QlieEntry>> {
|
||||
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<T: Read + Seek + std::fmt::Debug + 'static> Script for QliePackArchive<T> {
|
||||
|
||||
Reference in New Issue
Block a user