mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-07-08 01:31:53 +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>,
|
stream: Box<dyn ReadSeek + 'a>,
|
||||||
entry: &QlieEntry,
|
entry: &QlieEntry,
|
||||||
) -> Result<Box<dyn ReadDebug + 'a>>;
|
) -> Result<Box<dyn ReadDebug + 'a>>;
|
||||||
|
fn index_has_hash(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_encryption(
|
pub fn create_encryption(
|
||||||
@@ -43,6 +46,8 @@ pub fn create_encryption(
|
|||||||
match (major, minor) {
|
match (major, minor) {
|
||||||
(3, 1) => Ok(Box::new(Encryption31::new())),
|
(3, 1) => Ok(Box::new(Encryption31::new())),
|
||||||
(3, 0) => Ok(Box::new(Encryption30::new(game_key))),
|
(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!(
|
_ => Err(anyhow::anyhow!(
|
||||||
"Unsupported encryption version: {}.{}",
|
"Unsupported encryption version: {}.{}",
|
||||||
major,
|
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]
|
#[test]
|
||||||
fn test_compress_decompress() -> Result<()> {
|
fn test_compress_decompress() -> Result<()> {
|
||||||
let data = b"The quick brown fox jumps over the lazy dog.".repeat(100);
|
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 {
|
if major == 3 && minor == 0 {
|
||||||
game_key = encryption::find_key_data(filename)?;
|
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
|
// Read key
|
||||||
let mut key = 0;
|
let mut key = 0;
|
||||||
let mut qkey = None;
|
let mut qkey = None;
|
||||||
@@ -201,37 +201,31 @@ impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
|||||||
qkey = Some(qk);
|
qkey = Some(qk);
|
||||||
}
|
}
|
||||||
// Read entries
|
// Read entries
|
||||||
let mut entries = Vec::new();
|
let mut entries = if major >= 2 {
|
||||||
reader.seek(SeekFrom::Start(header.index_offset))?;
|
Self::read_entries(&mut reader, key, &header, archive_encoding, &encryption)?
|
||||||
for _ in 0..header.file_count {
|
} else {
|
||||||
let name_length = reader.read_u16()?;
|
let possible_encs: [Box<dyn Encryption>; 3] = [
|
||||||
let raw_name_length = if encryption.is_unicode() {
|
Box::new(encryption::Encryption10::new()),
|
||||||
name_length as usize * 2
|
Box::new(encryption::Encryption20::new_no_hash()),
|
||||||
} else {
|
Box::new(encryption::Encryption20::new()),
|
||||||
name_length as usize
|
];
|
||||||
};
|
let mut t = None;
|
||||||
let mut raw_name = reader.read_exact_vec(raw_name_length)?;
|
for enc in possible_encs {
|
||||||
let name = encryption.decrypt_name(&mut raw_name, key as i32, archive_encoding)?;
|
match Self::read_entries(&mut reader, key, &header, archive_encoding, &enc) {
|
||||||
let offset = reader.read_u64()?;
|
Ok(entries) => {
|
||||||
let size = reader.read_u32()?;
|
encryption = enc;
|
||||||
let unpacked_size = reader.read_u32()?;
|
t = Some(entries);
|
||||||
let is_packed = reader.read_u32()?;
|
break;
|
||||||
let is_encrypted = reader.read_u32()?;
|
}
|
||||||
let hash = reader.read_u32()?;
|
Err(_) => continue,
|
||||||
let entry = QlieEntry {
|
}
|
||||||
raw_name,
|
}
|
||||||
name,
|
t.ok_or_else(|| {
|
||||||
offset,
|
anyhow::anyhow!(
|
||||||
size,
|
"Failed to read Qlie Pack Archive entries with any encryption method"
|
||||||
unpacked_size,
|
)
|
||||||
is_packed,
|
})?
|
||||||
is_encrypted,
|
};
|
||||||
hash,
|
|
||||||
key,
|
|
||||||
common_key: None,
|
|
||||||
};
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
let mut common_key = None;
|
let mut common_key = None;
|
||||||
if major >= 3 {
|
if major >= 3 {
|
||||||
common_key = encryption::find_game_key(filename)?;
|
common_key = encryption::find_game_key(filename)?;
|
||||||
@@ -261,6 +255,48 @@ impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
|||||||
common_key,
|
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> {
|
impl<T: Read + Seek + std::fmt::Debug + 'static> Script for QliePackArchive<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user