mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 21:08:48 +08:00
Add Qlie Pack Archive (.pack) v3.1 unpack support
This commit is contained in:
@@ -14,8 +14,13 @@ pub trait WriteSeek: Write + Seek {}
|
||||
/// A trait for types that can be displayed in debug format and are also support downcasting.
|
||||
pub trait AnyDebug: std::fmt::Debug + std::any::Any {}
|
||||
|
||||
/// A trait for reading in a stream with debug format.
|
||||
pub trait ReadDebug: Read + std::fmt::Debug {}
|
||||
|
||||
impl<T: Read + Seek + std::fmt::Debug> ReadSeek for T {}
|
||||
|
||||
impl<T: Read + std::fmt::Debug> ReadDebug for T {}
|
||||
|
||||
impl<T: Write + Seek> WriteSeek for T {}
|
||||
|
||||
impl<T: std::fmt::Debug + std::any::Any> AnyDebug for T {}
|
||||
|
||||
@@ -168,6 +168,8 @@ lazy_static::lazy_static! {
|
||||
Box::new(entis_gls::csx::CSXScriptBuilder::new()),
|
||||
#[cfg(feature = "qlie")]
|
||||
Box::new(qlie::script::QlieScriptBuilder::new()),
|
||||
#[cfg(feature = "qlie-arc")]
|
||||
Box::new(qlie::archive::pack::QliePackArchiveBuilder::new()),
|
||||
];
|
||||
/// A list of all script extensions.
|
||||
pub static ref ALL_EXTS: Vec<String> =
|
||||
|
||||
3
src/scripts/qlie/archive/mod.rs
Normal file
3
src/scripts/qlie/archive/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Qlie Engine archive module
|
||||
#[allow(dead_code)]
|
||||
pub mod pack;
|
||||
475
src/scripts/qlie/archive/pack/encryption.rs
Normal file
475
src/scripts/qlie/archive/pack/encryption.rs
Normal file
@@ -0,0 +1,475 @@
|
||||
use super::types::*;
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use crate::utils::mmx::*;
|
||||
use anyhow::Result;
|
||||
use std::io::Read;
|
||||
|
||||
pub trait Hasher {
|
||||
fn update(&mut self, data: &[u8]) -> Result<()>;
|
||||
fn finalize(&mut self) -> Result<u32>;
|
||||
}
|
||||
|
||||
pub trait Encryption: std::fmt::Debug {
|
||||
fn is_unicode(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn compute_hash(&self, _data: &[u8]) -> Result<u32> {
|
||||
Ok(0)
|
||||
}
|
||||
fn create_hash(&self) -> Result<Box<dyn Hasher>> {
|
||||
Err(anyhow::anyhow!("Hasher not implemented"))
|
||||
}
|
||||
fn decrypt_name(&self, name: &mut [u8], hash: i32, encoding: Encoding) -> Result<String>;
|
||||
fn decrypt_entry<'a>(
|
||||
&self,
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
entry: &QlieEntry,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>>;
|
||||
}
|
||||
|
||||
pub fn create_encryption(major: u8, minor: u8) -> Result<Box<dyn Encryption>> {
|
||||
match (major, minor) {
|
||||
(3, 1) => Ok(Box::new(Encryption31::new())),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Unsupported encryption version: {}.{}",
|
||||
major,
|
||||
minor
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompress<'a>(data: Box<dyn ReadDebug + 'a>) -> Result<Box<dyn ReadDebug + 'a>> {
|
||||
Ok(Box::new(Decompressor::new(data)?))
|
||||
}
|
||||
|
||||
pub fn decrypt(data: &mut [u8], key: u32) -> Result<()> {
|
||||
let length = data.len();
|
||||
if length < 8 {
|
||||
// Nothing to decrypt
|
||||
return Ok(());
|
||||
}
|
||||
let mut data = MemWriterRef::new(data);
|
||||
const C1: u64 = 0xA73C5F9D;
|
||||
const C2: u64 = 0xCE24F523;
|
||||
const C3: u64 = 0xFEC9753E;
|
||||
let mut v5 = mmx_punpckldq2(C1);
|
||||
const V7: u64 = mmx_punpckldq2(C2);
|
||||
let mut v9 = mmx_punpckldq2(((length as u32).wrapping_add(key) as u64) ^ C3);
|
||||
for _ in 0..length / 8 {
|
||||
let d = data.peek_u64()?;
|
||||
v5 = mmx_p_add_d(v5, V7) ^ v9;
|
||||
v9 = d ^ v5;
|
||||
data.write_u64(v9)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_common_key(data: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut reader = MemReaderRef::new(data);
|
||||
let mut key = vec![0u8; 0x400];
|
||||
let mut writer = MemWriterRef::new(&mut key);
|
||||
for i in 0..0x100i32 {
|
||||
let temp = if (i % 3) != 0 {
|
||||
(i + 7) * -(i + 3)
|
||||
} else {
|
||||
(i + 7) * (i + 3)
|
||||
};
|
||||
writer.write_u32_at(i as u64 * 4, temp as u32)?;
|
||||
}
|
||||
let mut v1 = (reader.peek_u8_at(49)? % 0x49) as i32 + 0x80;
|
||||
let v2 = (reader.peek_u8_at(79)? % 7) as i32 + 7;
|
||||
let data_len = data.len() as i32;
|
||||
for i in 0..0x400 {
|
||||
v1 = (v1.wrapping_add(v2)) % data_len;
|
||||
key[i] ^= reader.peek_u8_at(v1 as u64)?;
|
||||
}
|
||||
// crate::utils::files::write_file("./testscripts/test.bin")?.write_all(&key)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Encryption31 {}
|
||||
|
||||
impl Encryption31 {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn create_table(length: usize, mut value: u32, is_v1: bool) -> Result<Vec<u8>> {
|
||||
let mut table = Vec::with_capacity(length);
|
||||
let key: u32 = if is_v1 { 0x8DF21431 } else { 0x8A77F473 };
|
||||
for _ in 0..length {
|
||||
let t = (key as u64).wrapping_mul((value as u64) ^ (key as u64));
|
||||
value = ((t >> 32) + t) as u32;
|
||||
table.push(value);
|
||||
}
|
||||
let mut mem = MemWriter::with_capacity(length * 4);
|
||||
for i in table {
|
||||
mem.write_u32(i)?;
|
||||
}
|
||||
Ok(mem.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encryption for Encryption31 {
|
||||
fn is_unicode(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn compute_hash(&self, data: &[u8]) -> Result<u32> {
|
||||
let mut hasher = Encryption31Hasher::new();
|
||||
hasher.update(data)?;
|
||||
Ok(hasher.finalize()?)
|
||||
}
|
||||
fn create_hash(&self) -> Result<Box<dyn Hasher>> {
|
||||
Ok(Box::new(Encryption31Hasher::new()))
|
||||
}
|
||||
fn decrypt_name(&self, name: &mut [u8], hash: i32, _encoding: Encoding) -> Result<String> {
|
||||
if name.len() % 2 != 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid name length for Unicode decryption"
|
||||
));
|
||||
}
|
||||
let char_len = name.len() / 2;
|
||||
let cl = char_len as i32;
|
||||
let temp = (cl.wrapping_mul(cl) ^ cl ^ 0x3e13 ^ (hash >> 16) ^ hash) & 0xFFFF;
|
||||
let mut key = temp;
|
||||
for i in 0..char_len {
|
||||
key = temp
|
||||
.wrapping_add(i as i32)
|
||||
.wrapping_add(key.wrapping_mul(8));
|
||||
name[i * 2] ^= key as u8;
|
||||
name[i * 2 + 1] ^= (key >> 8) as u8;
|
||||
}
|
||||
Ok(decode_to_string(Encoding::Utf16LE, &name, true)?)
|
||||
}
|
||||
fn decrypt_entry<'a>(
|
||||
&self,
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
entry: &QlieEntry,
|
||||
) -> Result<Box<dyn ReadDebug + 'a>> {
|
||||
match entry.is_encrypted {
|
||||
// No encryption
|
||||
0 => Ok(Box::new(stream)),
|
||||
1 => Ok(Box::new(Encryption31DecryptV1::new(
|
||||
stream,
|
||||
entry.size,
|
||||
entry.name.clone(),
|
||||
entry.key,
|
||||
)?)),
|
||||
2 => Ok(Box::new(Encryption31DecryptV2::new(
|
||||
stream,
|
||||
entry.size,
|
||||
entry.name.clone(),
|
||||
entry.key,
|
||||
entry
|
||||
.common_key
|
||||
.clone()
|
||||
.ok_or(anyhow::anyhow!("Missing common key"))?,
|
||||
)?)),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Unsupported encryption flag: {}",
|
||||
entry.is_encrypted
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Encryption31Hasher {
|
||||
hash: u64,
|
||||
key: u64,
|
||||
buffer: [u8; 8],
|
||||
buffer_len: usize,
|
||||
}
|
||||
|
||||
impl Encryption31Hasher {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
hash: 0,
|
||||
key: 0,
|
||||
buffer: [0; 8],
|
||||
buffer_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_internal(&mut self, data: u64) {
|
||||
const C: u64 = mmx_punpckldq2(0xA35793A7);
|
||||
self.hash = mmx_p_add_w(self.hash, C);
|
||||
let temp = mmx_p_add_w(self.key, self.hash ^ data);
|
||||
self.key = mmx_p_sll_d(temp, 3) | mmx_p_srl_d(temp, 0x1d);
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for Encryption31Hasher {
|
||||
fn update(&mut self, data: &[u8]) -> Result<()> {
|
||||
let mut used = 0;
|
||||
if self.buffer_len > 0 {
|
||||
let to_copy = (8 - self.buffer_len).min(data.len());
|
||||
self.buffer[self.buffer_len..self.buffer_len + to_copy]
|
||||
.copy_from_slice(&data[..to_copy]);
|
||||
self.buffer_len += to_copy;
|
||||
used += to_copy;
|
||||
}
|
||||
if self.buffer_len == 8 {
|
||||
let v = u64::from_le_bytes(self.buffer);
|
||||
self.update_internal(v);
|
||||
self.buffer_len = 0;
|
||||
}
|
||||
let round = (data.len() - used) / 8;
|
||||
let mut reader = MemReaderRef::new(&data[used..]);
|
||||
for _ in 0..round {
|
||||
let v = reader.read_u64()?;
|
||||
self.update_internal(v);
|
||||
used += 8;
|
||||
}
|
||||
let remaining = data.len() - used;
|
||||
if remaining > 0 {
|
||||
self.buffer[..remaining].copy_from_slice(&data[used..]);
|
||||
self.buffer_len = remaining;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finalize(&mut self) -> Result<u32> {
|
||||
let p1 = ((self.key as i16) as i32).wrapping_mul(((self.key >> 32) as i16) as i32);
|
||||
let p2 = (((self.key >> 16) as i16) as i32).wrapping_mul(((self.key >> 48) as i16) as i32);
|
||||
Ok((p1.wrapping_add(p2)) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Encryption31DecryptV1<'a> {
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
table: MemReader,
|
||||
v4: u32,
|
||||
v6: u64,
|
||||
}
|
||||
|
||||
impl<'a> Encryption31DecryptV1<'a> {
|
||||
pub fn new(
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
size: u32,
|
||||
name: String,
|
||||
key: u32,
|
||||
) -> Result<AlignedReader<8, Self>> {
|
||||
let mut v1 = 0x85F532u32;
|
||||
let mut v2 = 0x33F641u32;
|
||||
for (i, n) in name.encode_utf16().enumerate() {
|
||||
v1 = v1.wrapping_add((n as u32) << (i & 7));
|
||||
v2 ^= v1;
|
||||
}
|
||||
v2 = v2.wrapping_add(
|
||||
key ^ ((7 * (size & 0xFFFFFF))
|
||||
.wrapping_add(size)
|
||||
.wrapping_add(v1)
|
||||
.wrapping_add(v1 ^ size ^ 0x8F32DC)),
|
||||
);
|
||||
v2 = 9 * (v2 & 0xFFFFFF);
|
||||
let table = MemReader::new(Encryption31::create_table(0x40, v2, true)?);
|
||||
let v4 = 8 * (table.cpeek_u32_at(52)? & 0xF);
|
||||
let v6 = table.cpeek_u64_at(24)?;
|
||||
let inner = Self {
|
||||
stream,
|
||||
table,
|
||||
v4,
|
||||
v6,
|
||||
};
|
||||
Ok(AlignedReader::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for Encryption31DecryptV1<'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);
|
||||
for _ in 0..round {
|
||||
let d = writer.peek_u64()?;
|
||||
let temp = self.table.cpeek_u64_at(self.v4 as u64)?;
|
||||
let v7 = mmx_p_add_d(self.v6 ^ temp, temp);
|
||||
let v8 = d ^ v7;
|
||||
writer.write_u64(v8)?;
|
||||
self.v6 = mmx_p_add_w(mmx_p_sll_d(mmx_p_add_b(v7, v8) ^ v8, 1), v8);
|
||||
self.v4 = (self.v4 + 8) & 0x7F;
|
||||
}
|
||||
Ok(readed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Encryption31DecryptV2<'a> {
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
table: MemReader,
|
||||
v4: u32,
|
||||
v6: u64,
|
||||
common_key: MemReader,
|
||||
}
|
||||
|
||||
impl<'a> Encryption31DecryptV2<'a> {
|
||||
pub fn new(
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
size: u32,
|
||||
name: String,
|
||||
key: u32,
|
||||
common_key: Vec<u8>,
|
||||
) -> Result<AlignedReader<8, Self>> {
|
||||
let mut v1 = 0x86F7E2u32;
|
||||
let mut v2 = 0x4437F1u32;
|
||||
for (i, n) in name.encode_utf16().enumerate() {
|
||||
v1 = v1.wrapping_add((n as u32) << (i & 7));
|
||||
v2 ^= v1;
|
||||
}
|
||||
v2 = v2.wrapping_add(
|
||||
key ^ ((13 * (size & 0xFFFFFF))
|
||||
.wrapping_add(size)
|
||||
.wrapping_add(v1)
|
||||
.wrapping_add(v1 ^ size ^ 0x56E213)),
|
||||
);
|
||||
v2 = 13 * (v2 & 0xFFFFFF);
|
||||
let table = MemReader::new(Encryption31::create_table(0x40, v2, false)?);
|
||||
let v4 = 8 * (table.cpeek_u32_at(32)? & 0xD);
|
||||
let v6 = table.cpeek_u64_at(24)?;
|
||||
let inner = Self {
|
||||
stream,
|
||||
table,
|
||||
v4,
|
||||
v6,
|
||||
common_key: MemReader::new(common_key),
|
||||
};
|
||||
Ok(AlignedReader::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for Encryption31DecryptV2<'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);
|
||||
for _ in 0..round {
|
||||
let d = writer.peek_u64()?;
|
||||
let temp_index1 = ((self.v4 & 0xF) * 8) as u64;
|
||||
let temp_index2 = ((self.v4 & 0x7F) * 8) as u64;
|
||||
let temp = self.table.cpeek_u64_at(temp_index1)?
|
||||
^ self.common_key.cpeek_u64_at(temp_index2)?;
|
||||
let v7 = mmx_p_add_d(self.v6 ^ temp, temp);
|
||||
let v8 = d ^ v7;
|
||||
writer.write_u64(v8)?;
|
||||
self.v6 = mmx_p_add_w(mmx_p_sll_d(mmx_p_add_b(v7, v8) ^ v8, 1), v8);
|
||||
self.v4 = (self.v4 + 1) & 0x7F;
|
||||
}
|
||||
Ok(readed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Decompressor<'a> {
|
||||
stream: Box<dyn ReadDebug + 'a>,
|
||||
is_16bit: bool,
|
||||
temp: Vec<u8>,
|
||||
buf: Vec<u8>,
|
||||
buf_pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Decompressor<'a> {
|
||||
pub fn new(mut stream: Box<dyn ReadDebug + 'a>) -> Result<Self> {
|
||||
let sign = stream.read_u32()?;
|
||||
if sign != 0xFF435031 {
|
||||
return Err(anyhow::anyhow!("Invalid compression signature"));
|
||||
}
|
||||
let is_16bit = stream.read_u32()? & 1 != 0;
|
||||
let _unpacked_size = stream.read_u32()?;
|
||||
let temp = vec![0u8; 0x1000];
|
||||
Ok(Self {
|
||||
stream,
|
||||
is_16bit,
|
||||
temp,
|
||||
buf: Vec::new(),
|
||||
buf_pos: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn next_block(&mut self) -> Result<()> {
|
||||
self.buf.clear();
|
||||
self.buf_pos = 0;
|
||||
let mut buf = [0u8; 1];
|
||||
let readed = self.stream.read(&mut buf)?;
|
||||
if readed == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let mut buf_used = false;
|
||||
let mut table = [[0u8; 2]; 0x100];
|
||||
let mut i = 0u32;
|
||||
while i < 0x100 {
|
||||
let mut c = if !buf_used {
|
||||
buf_used = true;
|
||||
buf[0] as u32
|
||||
} else {
|
||||
self.stream.read_u8()? as u32
|
||||
};
|
||||
if c > 127 {
|
||||
c -= 127;
|
||||
while c > 0 {
|
||||
table[i as usize][0] = i as u8;
|
||||
c -= 1;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
c += 1;
|
||||
while c > 0 && i < 0x100 {
|
||||
table[i as usize][0] = self.stream.read_u8()?;
|
||||
if i as u8 != table[i as usize][0] {
|
||||
table[i as usize][1] = self.stream.read_u8()?;
|
||||
}
|
||||
c -= 1;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
let mut block_size = if self.is_16bit {
|
||||
self.stream.read_u16()? as usize
|
||||
} else {
|
||||
self.stream.read_u32()? as usize
|
||||
};
|
||||
let mut temp_length = 0usize;
|
||||
while block_size > 0 || temp_length > 0 {
|
||||
let c = if temp_length > 0 {
|
||||
temp_length -= 1;
|
||||
self.temp[temp_length]
|
||||
} else {
|
||||
block_size -= 1;
|
||||
self.stream.read_u8()?
|
||||
};
|
||||
if c == table[c as usize][0] {
|
||||
self.buf.push(c);
|
||||
} else {
|
||||
self.temp[temp_length] = table[c as usize][1];
|
||||
temp_length += 1;
|
||||
self.temp[temp_length] = table[c as usize][0];
|
||||
temp_length += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for Decompressor<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let mut used = 0;
|
||||
while used < buf.len() {
|
||||
if self.buf_pos >= self.buf.len() {
|
||||
self.next_block()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
if self.buf.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let to_copy = (self.buf.len() - self.buf_pos).min(buf.len() - used);
|
||||
buf[used..used + to_copy]
|
||||
.copy_from_slice(&self.buf[self.buf_pos..self.buf_pos + to_copy]);
|
||||
self.buf_pos += to_copy;
|
||||
used += to_copy;
|
||||
}
|
||||
Ok(used)
|
||||
}
|
||||
}
|
||||
295
src/scripts/qlie/archive/pack/mod.rs
Normal file
295
src/scripts/qlie/archive/pack/mod.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
//! Qlie Pack Archive (.pack)
|
||||
mod encryption;
|
||||
mod twister;
|
||||
mod types;
|
||||
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::struct_pack::*;
|
||||
use anyhow::Result;
|
||||
use encryption::Encryption;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use types::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QliePackArchiveBuilder {}
|
||||
|
||||
impl QliePackArchiveBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptBuilder for QliePackArchiveBuilder {
|
||||
fn default_encoding(&self) -> Encoding {
|
||||
Encoding::Cp932
|
||||
}
|
||||
|
||||
fn default_archive_encoding(&self) -> Option<Encoding> {
|
||||
Some(Encoding::Cp932)
|
||||
}
|
||||
|
||||
fn build_script(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
_filename: &str,
|
||||
_encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(QliePackArchive::new(
|
||||
MemReader::new(data),
|
||||
archive_encoding,
|
||||
config,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn build_script_from_file(
|
||||
&self,
|
||||
filename: &str,
|
||||
_encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
if filename == "-" {
|
||||
let data = crate::utils::files::read_file(filename)?;
|
||||
Ok(Box::new(QliePackArchive::new(
|
||||
MemReader::new(data),
|
||||
archive_encoding,
|
||||
config,
|
||||
)?))
|
||||
} else {
|
||||
let f = std::fs::File::open(filename)?;
|
||||
let reader = std::io::BufReader::new(f);
|
||||
Ok(Box::new(QliePackArchive::new(
|
||||
reader,
|
||||
archive_encoding,
|
||||
config,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_script_from_reader(
|
||||
&self,
|
||||
reader: Box<dyn ReadSeek>,
|
||||
_filename: &str,
|
||||
_encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(QliePackArchive::new(
|
||||
reader,
|
||||
archive_encoding,
|
||||
config,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["pack"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
&ScriptType::QliePack
|
||||
}
|
||||
|
||||
fn is_this_format(&self, filename: &str, _buf: &[u8], _buf_len: usize) -> Option<u8> {
|
||||
// Workround: Check only if filename exists in filesystem.
|
||||
// This means that we cannot detect .pack files in another archive.
|
||||
// Pack file header is at the end of file, so we cannot check signature here with header buffer.
|
||||
match is_this_format(filename) {
|
||||
Ok(true) => Some(30),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_archive(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given file is Qlie Pack Archive format
|
||||
pub fn is_this_format<P: AsRef<std::path::Path> + ?Sized>(path: &P) -> Result<bool> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() || !path.is_file() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
file.seek(SeekFrom::End(-0x1C))?;
|
||||
let header = QlieHeader::unpack(&mut file, false, Encoding::Utf8, &None)?;
|
||||
Ok(header.is_valid())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QliePackArchive<T: Read + Seek + std::fmt::Debug> {
|
||||
header: QlieHeader,
|
||||
encryption: Box<dyn Encryption>,
|
||||
reader: Arc<Mutex<T>>,
|
||||
qkey: Option<QlieKey>,
|
||||
entries: Vec<QlieEntry>,
|
||||
common_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
|
||||
reader.seek(SeekFrom::End(-0x1C))?;
|
||||
let header = QlieHeader::unpack(&mut reader, false, archive_encoding, &None)?;
|
||||
if !header.is_valid() {
|
||||
return Err(anyhow::anyhow!("Invalid Qlie Pack Archive header"));
|
||||
}
|
||||
let file_size = reader.stream_position()?;
|
||||
if header.index_offset > file_size - 0x1C {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid index offset in Qlie Pack Archive header"
|
||||
));
|
||||
}
|
||||
let major = header.major_version();
|
||||
let minor = header.minor_version();
|
||||
let encryption = encryption::create_encryption(major, minor)?;
|
||||
// Read key
|
||||
let mut key = 0;
|
||||
let mut qkey = None;
|
||||
if major >= 2 {
|
||||
reader.seek(SeekFrom::End(-0x440))?;
|
||||
let mut qk = QlieKey::unpack(&mut reader, false, archive_encoding, &None)?;
|
||||
if qk.hash_size as u64 > file_size || qk.hash_size < 0x44 {
|
||||
return Err(anyhow::anyhow!("Invalid Qlie Pack Archive key"));
|
||||
}
|
||||
if major >= 3 {
|
||||
key = encryption.compute_hash(&qk.key[..0x100])? & 0xFFFFFFF;
|
||||
}
|
||||
encryption::decrypt(&mut qk.signature, key)?;
|
||||
if &qk.signature != b"8hr48uky,8ugi8ewra4g8d5vbf5hb5s6" {
|
||||
eprintln!(
|
||||
"WARNING: Invalid Qlie Pack Archive key signature, decryption key may be incorrect"
|
||||
);
|
||||
crate::COUNTER.inc_warning();
|
||||
}
|
||||
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 {
|
||||
name,
|
||||
offset,
|
||||
size,
|
||||
unpacked_size,
|
||||
is_packed,
|
||||
is_encrypted,
|
||||
hash,
|
||||
key,
|
||||
common_key: None,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
let mut common_key = None;
|
||||
if major >= 3 && minor >= 1 {
|
||||
if let Some(common_key_entry) = entries
|
||||
.iter()
|
||||
.find(|e| e.name == "pack_keyfile_kfueheish15538fa9or.key")
|
||||
{
|
||||
reader.seek(SeekFrom::Start(common_key_entry.offset))?;
|
||||
let stream = StreamRegion::with_size(&mut reader, common_key_entry.size as u64)?;
|
||||
let mut decrypted = encryption.decrypt_entry(Box::new(stream), common_key_entry)?;
|
||||
if common_key_entry.is_packed != 0 {
|
||||
decrypted = encryption::decompress(decrypted)?;
|
||||
}
|
||||
let mut key_data = Vec::new();
|
||||
decrypted.read_to_end(&mut key_data)?;
|
||||
common_key = Some(encryption::get_common_key(&key_data)?);
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
header,
|
||||
encryption,
|
||||
reader: Arc::new(Mutex::new(reader)),
|
||||
qkey,
|
||||
entries,
|
||||
common_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + Seek + std::fmt::Debug + 'static> Script for QliePackArchive<T> {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Json
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn is_archive(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn iter_archive_filename<'a>(
|
||||
&'a self,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
|
||||
Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
|
||||
}
|
||||
|
||||
fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
|
||||
let mut entry = self
|
||||
.entries
|
||||
.get(index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid file index {} for Qlie Pack Archive", index))?
|
||||
.clone();
|
||||
if self.common_key.is_some() {
|
||||
entry.common_key = self.common_key.clone();
|
||||
}
|
||||
let stream = StreamRegion::with_size(
|
||||
MutexWrapper::new(self.reader.clone(), entry.offset),
|
||||
entry.size as u64,
|
||||
)?;
|
||||
let mut stream = self.encryption.decrypt_entry(Box::new(stream), &entry)?;
|
||||
if entry.is_packed != 0 {
|
||||
stream = encryption::decompress(stream)?;
|
||||
}
|
||||
Ok(Box::new(QliePackArchiveContent::new(stream, entry)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct QliePackArchiveContent<T: Read + std::fmt::Debug> {
|
||||
reader: T,
|
||||
entry: QlieEntry,
|
||||
}
|
||||
|
||||
impl<T: Read + std::fmt::Debug> QliePackArchiveContent<T> {
|
||||
pub fn new(reader: T, entry: QlieEntry) -> Self {
|
||||
Self { reader, entry }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + std::fmt::Debug> Read for QliePackArchiveContent<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.reader.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + std::fmt::Debug> ArchiveContent for QliePackArchiveContent<T> {
|
||||
fn name(&self) -> &str {
|
||||
&self.entry.name
|
||||
}
|
||||
}
|
||||
86
src/scripts/qlie/archive/pack/twister.rs
Normal file
86
src/scripts/qlie/archive/pack/twister.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
const DEFAULT_SEED: u32 = 5489;
|
||||
const STATE_LENGTH: usize = 64;
|
||||
const STATE_M: usize = 39;
|
||||
const MATRIX_A: u32 = 0x9908B0DF;
|
||||
const SIGN_MASK: u32 = 0x80000000;
|
||||
const LOWER_MASK: u32 = 0x7FFFFFFF;
|
||||
const TEMPERING_MASK_B: u32 = 0x9C4F88E3;
|
||||
const TEMPERING_MASK_C: u32 = 0xE7F70000;
|
||||
|
||||
pub struct MersenneTwister {
|
||||
mt: [u32; STATE_LENGTH],
|
||||
mti: usize,
|
||||
}
|
||||
|
||||
impl MersenneTwister {
|
||||
pub fn new(seed: u32) -> Self {
|
||||
let mut twister = Self {
|
||||
mt: [0; STATE_LENGTH],
|
||||
mti: STATE_LENGTH,
|
||||
};
|
||||
twister.s_rand(seed);
|
||||
twister
|
||||
}
|
||||
|
||||
pub fn s_rand(&mut self, seed: u32) {
|
||||
self.mt[0] = seed;
|
||||
for i in 1..STATE_LENGTH {
|
||||
self.mt[i] = (0x6611BC19u32.wrapping_mul(self.mt[i - 1] ^ (self.mt[i - 1] >> 30)))
|
||||
.wrapping_add(i as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xor_state(&mut self, hash: &[u8]) {
|
||||
let length = (hash.len() / 4).min(STATE_LENGTH);
|
||||
if length == 0 {
|
||||
return;
|
||||
}
|
||||
for i in 0..length {
|
||||
let part = u32::from_le_bytes([
|
||||
hash[i * 4],
|
||||
hash[i * 4 + 1],
|
||||
hash[i * 4 + 2],
|
||||
hash[i * 4 + 3],
|
||||
]);
|
||||
self.mt[i] ^= part;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rand(&mut self) -> u32 {
|
||||
const MAG01: [u32; 2] = [0, MATRIX_A];
|
||||
|
||||
if self.mti >= STATE_LENGTH {
|
||||
for kk in 0..(STATE_LENGTH - STATE_M) {
|
||||
let y = (self.mt[kk] & SIGN_MASK) | (self.mt[kk + 1] & LOWER_MASK) >> 1;
|
||||
self.mt[kk] = self.mt[kk + STATE_M] ^ y ^ MAG01[(self.mt[kk + 1] & 1) as usize];
|
||||
}
|
||||
for kk in (STATE_LENGTH - STATE_M)..(STATE_LENGTH - 1) {
|
||||
let y = (self.mt[kk] & SIGN_MASK) | (self.mt[kk + 1] & LOWER_MASK) >> 1;
|
||||
self.mt[kk] = self.mt[kk - (STATE_LENGTH - STATE_M)]
|
||||
^ y
|
||||
^ MAG01[(self.mt[kk + 1] & 1) as usize];
|
||||
}
|
||||
let y = (self.mt[STATE_LENGTH - 1] & SIGN_MASK) | (self.mt[0] & LOWER_MASK) >> 1;
|
||||
self.mt[STATE_LENGTH - 1] =
|
||||
self.mt[STATE_M - 1] ^ y ^ MAG01[(self.mt[STATE_LENGTH - 2] & 1) as usize];
|
||||
|
||||
self.mti = 0;
|
||||
}
|
||||
|
||||
let mut y = self.mt[self.mti];
|
||||
self.mti += 1;
|
||||
|
||||
y ^= y >> 11;
|
||||
y ^= (y << 7) & TEMPERING_MASK_B;
|
||||
y ^= (y << 15) & TEMPERING_MASK_C;
|
||||
y ^= y >> 18;
|
||||
|
||||
y
|
||||
}
|
||||
|
||||
pub fn rand64(&mut self) -> u64 {
|
||||
let low = self.rand() as u64;
|
||||
let high = self.rand() as u64;
|
||||
(high << 32) | low
|
||||
}
|
||||
}
|
||||
95
src/scripts/qlie/archive/pack/types.rs
Normal file
95
src/scripts/qlie/archive/pack/types.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use crate::ext::io::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::struct_pack::*;
|
||||
use anyhow::Result;
|
||||
use msg_tool_macro::{StructPack, StructUnpack};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
pub const HASH_VER_1_2_SIGNATURE: &[u8; 16] = b"HashVer1.2\x00\x00\x00\x00\x00\x00";
|
||||
pub const HASH_VER_1_3_SIGNATURE: &[u8; 16] = b"HashVer1.3\x00\x00\x00\x00\x00\x00";
|
||||
pub const HASH_VER_1_4_SIGNATURE: &[u8; 16] = b"HashVer1.4\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
/// HashVer 1.2
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieHash12 {
|
||||
pub signature: [u8; 16],
|
||||
/// Always 0x200
|
||||
pub const_: u32,
|
||||
pub file_count: u32,
|
||||
pub index_size: u32,
|
||||
#[pvec(u32)]
|
||||
pub hash_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// HashVer 1.3
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieHash13 {
|
||||
pub signature: [u8; 16],
|
||||
/// Always 0x100
|
||||
pub const_: u32,
|
||||
pub file_count: u32,
|
||||
pub index_size: u32,
|
||||
#[pvec(u32)]
|
||||
pub hash_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// HashVer 1.4
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieHash14 {
|
||||
pub signature: [u8; 16],
|
||||
/// Always 0x100
|
||||
pub const_: u32,
|
||||
pub file_count: u32,
|
||||
pub index_size: u32,
|
||||
pub hash_data_size: u32,
|
||||
pub is_compressed: u32,
|
||||
pub unk: [u8; 32],
|
||||
#[unpack_vec_len(hash_data_size)]
|
||||
#[pack_vec_len(self.hash_data_size)]
|
||||
pub hash_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieHeader {
|
||||
pub signature: [u8; 16],
|
||||
pub file_count: u32,
|
||||
pub index_offset: u64,
|
||||
}
|
||||
|
||||
impl QlieHeader {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.signature.starts_with(b"FilePackVer")
|
||||
&& self.signature[12] == b'.'
|
||||
&& &self.signature[14..] == b"\x00\x00"
|
||||
&& self.signature[11].is_ascii_digit()
|
||||
&& self.signature[13].is_ascii_digit()
|
||||
}
|
||||
|
||||
pub fn major_version(&self) -> u8 {
|
||||
self.signature[11] - b'0'
|
||||
}
|
||||
|
||||
pub fn minor_version(&self) -> u8 {
|
||||
self.signature[13] - b'0'
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieKey {
|
||||
pub signature: [u8; 32],
|
||||
pub hash_size: u32,
|
||||
pub key: [u8; 0x400],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QlieEntry {
|
||||
pub name: String,
|
||||
pub offset: u64,
|
||||
pub size: u32,
|
||||
pub unpacked_size: u32,
|
||||
pub is_packed: u32,
|
||||
pub is_encrypted: u32,
|
||||
pub hash: u32,
|
||||
pub key: u32,
|
||||
pub common_key: Option<Vec<u8>>,
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
//! Qlie Engine script module
|
||||
#[cfg(feature = "qlie-arc")]
|
||||
pub mod archive;
|
||||
pub mod script;
|
||||
|
||||
Reference in New Issue
Block a user