Add Qlie Pack Archive (.pack) v3.1 unpack support

This commit is contained in:
2026-01-29 18:16:10 +08:00
parent f6246576d0
commit 770a32bdde
15 changed files with 1270 additions and 5 deletions

View File

@@ -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 {}

View File

@@ -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> =

View File

@@ -0,0 +1,3 @@
//! Qlie Engine archive module
#[allow(dead_code)]
pub mod pack;

View 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)
}
}

View 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
}
}

View 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
}
}

View 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>>,
}

View File

@@ -1,2 +1,4 @@
//! Qlie Engine script module
#[cfg(feature = "qlie-arc")]
pub mod archive;
pub mod script;