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

@@ -943,6 +943,10 @@ pub trait ReadExt {
/// Reads data and checks if it matches the provided data.
fn read_and_equal(&mut self, data: &[u8]) -> Result<()>;
/// Reads as much data as possible into the provided buffer.
/// Returns the number of bytes read.
fn read_most(&mut self, buf: &mut [u8]) -> Result<usize>;
}
impl<T: Read> ReadExt for T {
@@ -1113,6 +1117,18 @@ impl<T: Read> ReadExt for T {
}
Ok(())
}
fn read_most(&mut self, buf: &mut [u8]) -> Result<usize> {
let mut total_read = 0;
while total_read < buf.len() {
match self.read(&mut buf[total_read..]) {
Ok(0) => break, // EOF reached
Ok(n) => total_read += n,
Err(e) => return Err(e),
}
}
Ok(total_read)
}
}
/// A trait to help to write data to a writer.
@@ -1681,6 +1697,14 @@ impl MemWriter {
}
}
/// Creates a new `MemWriter` with the given capacity.
pub fn with_capacity(capacity: usize) -> Self {
MemWriter {
data: Vec::with_capacity(capacity),
pos: 0,
}
}
/// Creates a new `MemWriter` with the given data.
pub fn from_vec(data: Vec<u8>) -> Self {
MemWriter { data, pos: 0 }
@@ -1791,6 +1815,102 @@ impl CPeek for MemWriter {
}
}
/// A memory reader that can read data from a slice of bytes.
pub struct MemWriterRef<'a> {
/// The data to read from.
pub data: &'a mut [u8],
/// The current position in the data.
pub pos: usize,
}
impl<'a> MemWriterRef<'a> {
/// Creates a new `MemWriterRef` with the given data.
pub fn new(data: &'a mut [u8]) -> Self {
MemWriterRef { data, pos: 0 }
}
}
impl<'a> Read for MemWriterRef<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.pos >= self.data.len() {
return Ok(0);
}
let bytes_to_read = buf.len().min(self.data.len() - self.pos);
let mut bu = &self.data[self.pos..self.pos + bytes_to_read];
bu.read(buf)?;
self.pos += bytes_to_read;
Ok(bytes_to_read)
}
}
impl<'a> Seek for MemWriterRef<'a> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
match pos {
SeekFrom::Start(offset) => {
if offset > self.data.len() as u64 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek position is beyond the end of the data",
));
}
self.pos = offset as usize;
}
SeekFrom::End(offset) => {
let end_pos = self.data.len() as i64 + offset;
if end_pos < 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek from end resulted in negative position",
));
}
if end_pos as usize > self.data.len() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek position is beyond the end of the data",
));
}
self.pos = end_pos as usize;
}
SeekFrom::Current(offset) => {
let new_pos = (self.pos as i64 + offset) as usize;
if new_pos > self.data.len() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek position is beyond the end of the data",
));
}
self.pos = new_pos;
}
}
Ok(self.pos as u64)
}
fn stream_position(&mut self) -> Result<u64> {
Ok(self.pos as u64)
}
fn rewind(&mut self) -> Result<()> {
self.pos = 0;
Ok(())
}
}
impl Write for MemWriterRef<'_> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if self.pos >= self.data.len() {
return Ok(0);
}
let bytes_to_write = buf.len().min(self.data.len() - self.pos);
self.data[self.pos..self.pos + bytes_to_write].copy_from_slice(&buf[..bytes_to_write]);
self.pos += bytes_to_write;
Ok(bytes_to_write)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
/// A region of a stream that can be read/write and seeked within a specified range.
#[derive(Debug)]
pub struct StreamRegion<T: Seek> {
@@ -2404,3 +2524,62 @@ impl<'a, T: Write> Write for TrackStream<'a, T> {
self.inner.flush()
}
}
/// A reader that forwards reads to an inner reader, but ensures that all reads are aligned to a specified alignment boundary.
#[derive(Debug)]
pub struct AlignedReader<const A: usize, T: Read> {
inner: T,
buffer: [u8; A],
buffer_size: usize,
}
impl<const A: usize, T: Read> AlignedReader<A, T> {
/// Creates a new `AlignedReader` with the given inner reader.
pub fn new(inner: T) -> Self {
AlignedReader {
inner,
buffer: [0; A],
buffer_size: 0,
}
}
}
impl<const A: usize, T: Read> Read for AlignedReader<A, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let mut total_read = 0;
let mut needed = buf.len();
if needed <= self.buffer_size {
buf[..needed].copy_from_slice(&self.buffer[..needed]);
self.buffer.copy_within(needed..self.buffer_size, 0);
self.buffer_size -= needed;
return Ok(needed);
}
if self.buffer_size > 0 {
buf[..self.buffer_size].copy_from_slice(&self.buffer[..self.buffer_size]);
total_read += self.buffer_size;
needed -= self.buffer_size;
self.buffer_size = 0;
}
let read_len = needed / A * A;
if read_len > 0 {
let readed = self
.inner
.read(&mut buf[total_read..total_read + read_len])?;
total_read += readed;
needed -= readed;
// EOF reached
if readed < read_len {
return Ok(total_read);
}
}
if needed > 0 {
let readed = self.inner.read(&mut self.buffer)?;
let to_copy = needed.min(readed);
buf[total_read..total_read + to_copy].copy_from_slice(&self.buffer[..to_copy]);
total_read += to_copy;
self.buffer_size = readed - to_copy;
self.buffer.copy_within(to_copy..readed, 0);
}
Ok(total_read)
}
}

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;

View File

@@ -778,6 +778,9 @@ pub enum ScriptType {
#[cfg(feature = "qlie")]
/// Qlie Engine Scenario script (.s)
Qlie,
#[cfg(feature = "qlie-arc")]
/// Qlie Pack Archive (.pack)
QliePack,
#[cfg(feature = "silky")]
/// Silky Engine Mes script
Silky,

74
src/utils/mmx.rs Normal file
View File

@@ -0,0 +1,74 @@
//! MMX Operations in pure Rust
/// Packed Add of Bytes (8-bit integers)
pub const fn mmx_p_add_b(a: u64, b: u64) -> u64 {
let mut mask = 0xFF;
let mut r = ((a & mask).wrapping_add(b & mask)) & mask; // First byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Second byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Third byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Fourth byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Fifth byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Sixth byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Seventh byte
mask <<= 8;
r |= ((a & mask).wrapping_add(b & mask)) & mask; // Eighth byte
r
}
/// Packed Add of Doublewords (32-bit integers)
pub const fn mmx_p_add_d(a: u64, b: u64) -> u64 {
let mut mask = 0xffffffff;
let r = ((a & mask).wrapping_add(b & mask)) & mask;
mask <<= 32;
r | ((a & mask).wrapping_add(b & mask)) & mask
}
/// Packed Add of Words (16-bit integers)
pub const fn mmx_p_add_w(a: u64, b: u64) -> u64 {
let mut mask = 0xFFFF;
let mut r = ((a & mask).wrapping_add(b & mask)) & mask;
mask <<= 16;
r |= ((a & mask).wrapping_add(b & mask)) & mask;
mask <<= 16;
r |= ((a & mask).wrapping_add(b & mask)) & mask;
mask <<= 16;
r |= ((a & mask).wrapping_add(b & mask)) & mask;
r
}
/// Packed Shift Left Logical of Doublewords (32-bit integers)
pub const fn mmx_p_sll_d(a: u64, mut count: u32) -> u64 {
count &= 0x1F;
let mut mask = (0xFFFFFFFFu32 << count) as u64;
mask |= mask << 32;
(a << count) & mask
}
/// Packed Shift Right Logical of Doublewords (32-bit integers)
pub const fn mmx_p_srl_d(a: u64, mut count: u32) -> u64 {
count &= 0x1F;
let mut mask = (0xFFFFFFFFu32 >> count) as u64;
mask |= mask << 32;
(a >> count) & mask
}
/// Packed Unpack Low Doubleword
pub const fn mmx_punpckldq(a: u64, b: u64) -> u64 {
(a & 0xFFFFFFFF) | (b & 0xFFFFFFFF) << 32
}
/// Packed Unpack Low Doubleword with itself
pub const fn mmx_punpckldq2(a: u64) -> u64 {
mmx_punpckldq(a, a)
}
#[test]
fn test_mmx() {
assert_eq!(mmx_punpckldq(0x1, 0x2), 0x200000001);
}

View File

@@ -21,6 +21,8 @@ pub mod jxl;
#[cfg(feature = "lossless-audio")]
pub mod lossless_audio;
mod macros;
#[cfg(feature = "utils-mmx")]
pub mod mmx;
pub mod name_replacement;
pub mod num_range;
#[cfg(feature = "utils-pcm")]