mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-15 17:44:25 +08:00
Add pack support for Qlie Pack Archive (.pack) v3.1
This commit is contained in:
@@ -589,6 +589,12 @@ pub trait Script: std::fmt::Debug + std::any::Any {
|
||||
|
||||
/// A trait for creating archives.
|
||||
pub trait Archive {
|
||||
/// Returns an iterator of a list of filenames must writed before other files.
|
||||
///
|
||||
/// Should return None if no such requirement.
|
||||
fn prelist<'a>(&'a self) -> Result<Option<Box<dyn Iterator<Item = Result<String>> + 'a>>> {
|
||||
Ok(None)
|
||||
}
|
||||
/// Creates a new file in the archive.
|
||||
///
|
||||
/// size is optional, if provided, size must be exactly the size of the file to be created.
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use crate::utils::mmx::*;
|
||||
use anyhow::Result;
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub trait Hasher {
|
||||
fn update(&mut self, data: &[u8]) -> Result<()>;
|
||||
@@ -67,6 +67,29 @@ pub fn decrypt(data: &mut [u8], key: u32) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encrypt(data: &mut [u8], key: u32) -> Result<()> {
|
||||
let length = data.len();
|
||||
if length < 8 {
|
||||
// Nothing to encrypt
|
||||
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 mut d = data.peek_u64()?;
|
||||
v5 = mmx_p_add_d(v5, V7) ^ v9;
|
||||
v9 = d;
|
||||
d ^= v5;
|
||||
data.write_u64(d)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_common_key(data: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut reader = MemReaderRef::new(data);
|
||||
let mut key = vec![0u8; 0x400];
|
||||
@@ -112,6 +135,44 @@ impl Encryption31 {
|
||||
}
|
||||
Ok(mem.into_inner())
|
||||
}
|
||||
|
||||
pub fn compute_name_hash(&self, name: &[u16]) -> Result<u32> {
|
||||
let mut v2 = 0u32;
|
||||
let mut v3 = name.len() as u32;
|
||||
let mut v4 = 1u32;
|
||||
if v3 > 0 {
|
||||
loop {
|
||||
let n = (name[(v4 - 1) as usize] as u32) << (v4 & 7);
|
||||
v2 = v2.wrapping_add(n) & 0x3FFFFFFF;
|
||||
v4 += 1;
|
||||
v3 -= 1;
|
||||
if v3 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(v2)
|
||||
}
|
||||
|
||||
pub fn encrypt_name(&self, name: &mut [u8], hash: i32) -> Result<()> {
|
||||
if name.len() % 2 != 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid name length for Unicode encryption"
|
||||
));
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encryption for Encryption31 {
|
||||
@@ -298,6 +359,67 @@ impl<'a> Read for Encryption31DecryptV1<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Encryption31EncryptV1<T: Write> {
|
||||
stream: T,
|
||||
table: MemReader,
|
||||
v4: u32,
|
||||
v6: u64,
|
||||
}
|
||||
|
||||
impl<T: Write> Encryption31EncryptV1<T> {
|
||||
pub fn new(stream: T, size: u32, name: String, key: u32) -> Result<AlignedWriter<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(AlignedWriter::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Write for Encryption31EncryptV1<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let round = buf.len() / 8;
|
||||
let mut reader = MemReaderRef::new(buf);
|
||||
for _ in 0..round {
|
||||
let d = reader.read_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;
|
||||
self.stream.write_u64(v8)?;
|
||||
self.v6 = mmx_p_add_w(mmx_p_sll_d(mmx_p_add_b(v7, d) ^ d, 1), d);
|
||||
self.v4 = (self.v4 + 8) & 0x7F;
|
||||
}
|
||||
let remain = buf.len() % 8;
|
||||
if remain > 0 {
|
||||
self.stream.write_all(&buf[buf.len() - remain..])?;
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.stream.flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Encryption31DecryptV2<'a> {
|
||||
stream: Box<dyn ReadSeek + 'a>,
|
||||
@@ -363,6 +485,78 @@ impl<'a> Read for Encryption31DecryptV2<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Encryption31EncryptV2<T: Write> {
|
||||
stream: T,
|
||||
table: MemReader,
|
||||
v4: u32,
|
||||
v6: u64,
|
||||
common_key: MemReader,
|
||||
}
|
||||
|
||||
impl<T: Write> Encryption31EncryptV2<T> {
|
||||
pub fn new(
|
||||
stream: T,
|
||||
size: u32,
|
||||
name: String,
|
||||
key: u32,
|
||||
common_key: Vec<u8>,
|
||||
) -> Result<AlignedWriter<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(AlignedWriter::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Write for Encryption31EncryptV2<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let round = buf.len() / 8;
|
||||
let mut reader = MemReaderRef::new(buf);
|
||||
for _ in 0..round {
|
||||
let d = reader.read_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;
|
||||
self.stream.write_u64(v8)?;
|
||||
self.v6 = mmx_p_add_w(mmx_p_sll_d(mmx_p_add_b(v7, d) ^ d, 1), d);
|
||||
self.v4 = (self.v4 + 1) & 0x7F;
|
||||
}
|
||||
let remain = buf.len() % 8;
|
||||
if remain > 0 {
|
||||
self.stream.write_all(&buf[buf.len() - remain..])?;
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.stream.flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Decompressor<'a> {
|
||||
stream: Box<dyn ReadDebug + 'a>,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod encryption;
|
||||
mod twister;
|
||||
mod types;
|
||||
mod v31;
|
||||
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
@@ -110,6 +111,20 @@ impl ScriptBuilder for QliePackArchiveBuilder {
|
||||
fn is_archive(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn create_archive(
|
||||
&self,
|
||||
filename: &str,
|
||||
files: &[&str],
|
||||
_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Archive>> {
|
||||
let f = std::fs::File::create(filename)?;
|
||||
let buf = std::io::BufWriter::new(f);
|
||||
Ok(Box::new(v31::QliePackArchiveWriterV31::new(
|
||||
buf, files, config,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given file is Qlie Pack Archive format
|
||||
@@ -163,7 +178,7 @@ impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
key = encryption.compute_hash(&qk.key[..0x100])? & 0xFFFFFFF;
|
||||
}
|
||||
encryption::decrypt(&mut qk.signature, key)?;
|
||||
if &qk.signature != b"8hr48uky,8ugi8ewra4g8d5vbf5hb5s6" {
|
||||
if &qk.signature != QLIE_KEY_SIGNATURE {
|
||||
eprintln!(
|
||||
"WARNING: Invalid Qlie Pack Archive key signature, decryption key may be incorrect"
|
||||
);
|
||||
@@ -204,10 +219,7 @@ impl<T: Read + Seek + std::fmt::Debug> QliePackArchive<T> {
|
||||
}
|
||||
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")
|
||||
{
|
||||
if let Some(common_key_entry) = entries.iter().find(|e| e.name == QLIE_KEY_FILE) {
|
||||
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)?;
|
||||
|
||||
@@ -8,6 +8,8 @@ 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";
|
||||
pub const QLIE_KEY_SIGNATURE: &[u8; 32] = b"8hr48uky,8ugi8ewra4g8d5vbf5hb5s6";
|
||||
pub const QLIE_KEY_FILE: &'static str = "pack_keyfile_kfueheish15538fa9or.key";
|
||||
|
||||
/// HashVer 1.2
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
@@ -37,8 +39,7 @@ pub struct QlieHash13 {
|
||||
#[derive(StructPack, StructUnpack, Debug, Clone)]
|
||||
pub struct QlieHash14 {
|
||||
pub signature: [u8; 16],
|
||||
/// Always 0x100
|
||||
pub const_: u32,
|
||||
pub table_size: u32,
|
||||
pub file_count: u32,
|
||||
pub index_size: u32,
|
||||
pub hash_data_size: u32,
|
||||
@@ -81,7 +82,7 @@ pub struct QlieKey {
|
||||
pub key: [u8; 0x400],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct QlieEntry {
|
||||
pub name: String,
|
||||
pub offset: u64,
|
||||
|
||||
586
src/scripts/qlie/archive/pack/v31.rs
Normal file
586
src/scripts/qlie/archive/pack/v31.rs
Normal file
@@ -0,0 +1,586 @@
|
||||
use super::encryption::*;
|
||||
use super::types::*;
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
use std::io::{Seek, Write};
|
||||
|
||||
struct MListEntry<T> {
|
||||
back: *mut MListEntry<T>,
|
||||
next: *mut MListEntry<T>,
|
||||
data: T,
|
||||
}
|
||||
|
||||
struct MList<T> {
|
||||
head: *mut MListEntry<T>,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl<T> MList<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
head: std::ptr::null_mut(),
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, data: T, way: bool) -> usize {
|
||||
let entry = Box::new(MListEntry {
|
||||
back: std::ptr::null_mut(),
|
||||
next: std::ptr::null_mut(),
|
||||
data,
|
||||
});
|
||||
let entry_ptr = Box::into_raw(entry);
|
||||
if self.head.is_null() {
|
||||
self.head = entry_ptr;
|
||||
unsafe {
|
||||
(*self.head).back = self.head;
|
||||
(*self.head).next = self.head;
|
||||
}
|
||||
} else {
|
||||
if way {
|
||||
unsafe {
|
||||
(*(*self.head).back).next = entry_ptr;
|
||||
(*entry_ptr).back = (*self.head).back;
|
||||
(*entry_ptr).next = self.head;
|
||||
(*self.head).back = entry_ptr;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
(*(*self.head).back).next = entry_ptr;
|
||||
(*entry_ptr).back = (*self.head).back;
|
||||
(*entry_ptr).next = self.head;
|
||||
(*self.head).back = entry_ptr;
|
||||
self.head = entry_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.depth += 1;
|
||||
self.depth
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, way: bool) -> Option<T> {
|
||||
if self.head.is_null() {
|
||||
return None;
|
||||
}
|
||||
if self.depth > 0 {
|
||||
self.depth -= 1;
|
||||
let ret;
|
||||
if way {
|
||||
unsafe {
|
||||
ret = (*self.head).back;
|
||||
(*(*ret).back).next = (*ret).next;
|
||||
(*self.head).back = (*ret).back;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
ret = self.head;
|
||||
(*(*ret).back).next = (*ret).next;
|
||||
(*(*ret).next).back = (*ret).back;
|
||||
self.head = (*ret).next;
|
||||
}
|
||||
}
|
||||
if self.depth == 0 {
|
||||
self.head = std::ptr::null_mut();
|
||||
}
|
||||
let boxed = unsafe { Box::from_raw(ret) };
|
||||
return Some(boxed.data);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for MList<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.head.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut current = self.head;
|
||||
loop {
|
||||
unsafe {
|
||||
let next = (*current).next;
|
||||
let _ = Box::from_raw(current);
|
||||
if next == self.head {
|
||||
break;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QliePackArchiveWriterV31<T: Write + Seek> {
|
||||
writer: T,
|
||||
encryption: Encryption31,
|
||||
qkey: QlieKey,
|
||||
header: QlieHeader,
|
||||
hash: QlieHash14,
|
||||
has_key_file: bool,
|
||||
entries: Vec<QlieEntry>,
|
||||
key: u32,
|
||||
common_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
struct FilenameEntry {
|
||||
name: Vec<u16>,
|
||||
hash: u32,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
fn get_pos(hash: u32, count: u32) -> u32 {
|
||||
let v = (hash as u16 as u32)
|
||||
.wrapping_add(hash >> 8)
|
||||
.wrapping_add(hash >> 16);
|
||||
v % count
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> QliePackArchiveWriterV31<T> {
|
||||
pub fn new(writer: T, files: &[&str], config: &ExtraConfig) -> Result<Self> {
|
||||
let has_key_file = files.iter().any(|f| *f == QLIE_KEY_FILE);
|
||||
let mut file_count = files.len() as u32;
|
||||
if !has_key_file {
|
||||
if config.qlie_pack_keyfile.is_none() {
|
||||
anyhow::bail!(
|
||||
"Qlie Pack Archive key file is required but not provided. Put a key file named '{}' in the directory or specify the path using '--qlie-pack-keyfile' option.",
|
||||
QLIE_KEY_FILE
|
||||
);
|
||||
}
|
||||
// Add 1 for the key file
|
||||
file_count += 1;
|
||||
}
|
||||
let header = QlieHeader {
|
||||
signature: *b"FilePackVer3.1\x00\x00",
|
||||
file_count,
|
||||
index_offset: 0,
|
||||
};
|
||||
let encryption = Encryption31::new();
|
||||
let mut qkey = QlieKey {
|
||||
signature: *QLIE_KEY_SIGNATURE,
|
||||
hash_size: 0,
|
||||
key: [0; 0x400],
|
||||
};
|
||||
rand::rng().fill(&mut qkey.key[..0x100]);
|
||||
let key = encryption.compute_hash(&qkey.key[..0x100])? & 0xFFFFFFF;
|
||||
encrypt(&mut qkey.signature, key)?;
|
||||
let mut entries = Vec::new();
|
||||
let mut list = Vec::with_capacity(256);
|
||||
for _ in 0..256 {
|
||||
list.push(MList::<FilenameEntry>::new());
|
||||
}
|
||||
let key_entry = QlieEntry {
|
||||
name: QLIE_KEY_FILE.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
entries.push(key_entry);
|
||||
let key_filename: Vec<_> = QLIE_KEY_FILE.encode_utf16().collect();
|
||||
let key_hash = encryption.compute_name_hash(&key_filename)?;
|
||||
let key_name_entry = FilenameEntry {
|
||||
name: key_filename,
|
||||
hash: key_hash,
|
||||
index: 0,
|
||||
};
|
||||
let pos = get_pos(key_hash, 256);
|
||||
list[pos as usize].push(key_name_entry, true);
|
||||
for name in files {
|
||||
if *name == QLIE_KEY_FILE {
|
||||
continue;
|
||||
}
|
||||
let filename: Vec<_> = name.encode_utf16().collect();
|
||||
let name_hash = encryption.compute_name_hash(&filename)?;
|
||||
let entry = QlieEntry {
|
||||
name: name.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
entries.push(entry);
|
||||
let name_entry = FilenameEntry {
|
||||
name: filename,
|
||||
hash: name_hash,
|
||||
index: (entries.len() - 1) as u32,
|
||||
};
|
||||
let pos = get_pos(name_hash, 256);
|
||||
list[pos as usize].push(name_entry, true);
|
||||
}
|
||||
let mut hash_data = MemWriter::new();
|
||||
for mut list in list {
|
||||
hash_data.write_u32(list.depth as u32)?;
|
||||
while let Some(entry) = list.pop(false) {
|
||||
hash_data.write_u16(entry.name.len() as u16)?;
|
||||
hash_data.write_struct(&entry.name, false, Encoding::Utf16LE, &None)?;
|
||||
hash_data.write_u64(entry.index as u64 * 4)?;
|
||||
hash_data.write_u32(entry.hash)?;
|
||||
}
|
||||
}
|
||||
for i in 0..file_count {
|
||||
hash_data.write_u32(i)?;
|
||||
}
|
||||
let mut hash_data = hash_data.into_inner();
|
||||
encrypt(&mut hash_data, 0x0428)?;
|
||||
let hash = QlieHash14 {
|
||||
signature: *HASH_VER_1_4_SIGNATURE,
|
||||
table_size: 256,
|
||||
file_count: header.file_count,
|
||||
index_size: header.file_count * 4,
|
||||
hash_data_size: hash_data.len() as u32,
|
||||
is_compressed: 0,
|
||||
unk: [0; 32],
|
||||
hash_data,
|
||||
};
|
||||
qkey.hash_size = hash.hash_data_size + 68;
|
||||
let mut inner = Self {
|
||||
writer,
|
||||
encryption,
|
||||
qkey,
|
||||
header,
|
||||
hash,
|
||||
has_key_file,
|
||||
entries,
|
||||
key,
|
||||
common_key: None,
|
||||
};
|
||||
if !has_key_file {
|
||||
let key_path = config.qlie_pack_keyfile.as_ref().unwrap();
|
||||
let key_data = std::fs::read(key_path)?;
|
||||
inner.write_key(key_data)?;
|
||||
}
|
||||
Ok(inner)
|
||||
}
|
||||
|
||||
fn write_key(&mut self, key_data: Vec<u8>) -> Result<()> {
|
||||
let entry = &mut self.entries[0];
|
||||
entry.size = key_data.len() as u32;
|
||||
entry.offset = self.writer.stream_position()?;
|
||||
entry.unpacked_size = entry.size;
|
||||
entry.is_packed = 0;
|
||||
entry.is_encrypted = 1;
|
||||
self.common_key = Some(get_common_key(&key_data)?);
|
||||
let hasher = Encryption31Hasher::new();
|
||||
let size = entry.size;
|
||||
let compute = EntryWriter {
|
||||
entry,
|
||||
inner: &mut self.writer,
|
||||
hasher,
|
||||
};
|
||||
let mut encryptor =
|
||||
Encryption31EncryptV1::new(compute, size, QLIE_KEY_FILE.to_string(), self.key)?;
|
||||
encryptor.write_all(&key_data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Writer<'a> {
|
||||
inner: Box<dyn Write + 'a>,
|
||||
mem: MemWriter,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Writer<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Writer").field("mem", &self.mem).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for Writer<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.mem.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.mem.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Seek for Writer<'a> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.mem.seek(pos)
|
||||
}
|
||||
|
||||
fn stream_position(&mut self) -> std::io::Result<u64> {
|
||||
self.mem.stream_position()
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> std::io::Result<()> {
|
||||
self.mem.rewind()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for Writer<'a> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.inner.write_all(&self.mem.data);
|
||||
let _ = self.inner.flush();
|
||||
}
|
||||
}
|
||||
|
||||
struct Writer2<'a, T: Write + Seek> {
|
||||
inner: &'a mut QliePackArchiveWriterV31<T>,
|
||||
entry_idx: usize,
|
||||
mem: MemWriter,
|
||||
is_v1: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Writer2<'a, T> {
|
||||
fn close(&mut self) -> Result<()> {
|
||||
let entry = &mut self.inner.entries[self.entry_idx];
|
||||
entry.size = self.mem.data.len() as u32;
|
||||
entry.offset = self.inner.writer.stream_position()?;
|
||||
entry.unpacked_size = entry.size;
|
||||
entry.is_packed = 0;
|
||||
let hasher = Encryption31Hasher::new();
|
||||
let size = entry.size;
|
||||
let compute = EntryWriter {
|
||||
entry,
|
||||
inner: &mut self.inner.writer,
|
||||
hasher,
|
||||
};
|
||||
if self.is_v1 {
|
||||
compute.entry.is_encrypted = 1;
|
||||
let name = compute.entry.name.clone();
|
||||
let mut encryptor = Encryption31EncryptV1::new(compute, size, name, self.inner.key)?;
|
||||
encryptor.write_all(&self.mem.data)?;
|
||||
self.inner.common_key = Some(get_common_key(&self.mem.data)?);
|
||||
} else {
|
||||
compute.entry.is_encrypted = 2;
|
||||
let name = compute.entry.name.clone();
|
||||
let common_key = self
|
||||
.inner
|
||||
.common_key
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Common key is not available"))?;
|
||||
let mut encryptor = Encryption31EncryptV2::new(
|
||||
compute,
|
||||
size,
|
||||
name,
|
||||
self.inner.key,
|
||||
common_key.to_vec(),
|
||||
)?;
|
||||
encryptor.write_all(&self.mem.data)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> std::fmt::Debug for Writer2<'_, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Writer").field("mem", &self.mem).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Write for Writer2<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.mem.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.mem.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Seek for Writer2<'a, T> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.mem.seek(pos)
|
||||
}
|
||||
|
||||
fn stream_position(&mut self) -> std::io::Result<u64> {
|
||||
self.mem.stream_position()
|
||||
}
|
||||
|
||||
fn rewind(&mut self) -> std::io::Result<()> {
|
||||
self.mem.rewind()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Write + Seek> Drop for Writer2<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.close();
|
||||
}
|
||||
}
|
||||
|
||||
struct EntryWriter<'a, T: Write> {
|
||||
entry: &'a mut QlieEntry,
|
||||
inner: T,
|
||||
hasher: Encryption31Hasher,
|
||||
}
|
||||
|
||||
impl<'a, T: Write> Write for EntryWriter<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let writed = self.inner.write(buf)?;
|
||||
self.hasher
|
||||
.update(&buf[..writed])
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
Ok(writed)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Write> Drop for EntryWriter<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(hash) = self.hasher.finalize() {
|
||||
self.entry.hash = hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + Seek> Archive for QliePackArchiveWriterV31<T> {
|
||||
fn prelist<'a>(&'a self) -> Result<Option<Box<dyn Iterator<Item = Result<String>> + 'a>>> {
|
||||
if !self.has_key_file {
|
||||
Ok(None)
|
||||
} else {
|
||||
let iter = std::iter::once(Ok(QLIE_KEY_FILE.to_string()));
|
||||
Ok(Some(Box::new(iter)))
|
||||
}
|
||||
}
|
||||
|
||||
fn new_file<'a>(
|
||||
&'a mut self,
|
||||
name: &str,
|
||||
size: Option<u64>,
|
||||
) -> Result<Box<dyn WriteSeek + 'a>> {
|
||||
let inner = self.new_file_non_seek(name, size)?;
|
||||
Ok(Box::new(Writer {
|
||||
inner,
|
||||
mem: MemWriter::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_file_non_seek<'a>(
|
||||
&'a mut self,
|
||||
name: &str,
|
||||
size: Option<u64>,
|
||||
) -> Result<Box<dyn Write + 'a>> {
|
||||
if self.common_key.is_none() {
|
||||
if name != QLIE_KEY_FILE {
|
||||
anyhow::bail!("Common key is not available before writing key file");
|
||||
}
|
||||
let entry_idx = self
|
||||
.entries
|
||||
.iter()
|
||||
.position(|e| e.name == name)
|
||||
.ok_or_else(|| anyhow::anyhow!("File {} not found in entries", name))?;
|
||||
return Ok(Box::new(Writer2 {
|
||||
inner: self,
|
||||
entry_idx,
|
||||
mem: MemWriter::new(),
|
||||
is_v1: true,
|
||||
}));
|
||||
}
|
||||
if size.is_none() {
|
||||
let entry_idx = self
|
||||
.entries
|
||||
.iter()
|
||||
.position(|e| e.name == name)
|
||||
.ok_or_else(|| anyhow::anyhow!("File {} not found in entries", name))?;
|
||||
return Ok(Box::new(Writer2 {
|
||||
inner: self,
|
||||
entry_idx,
|
||||
mem: MemWriter::new(),
|
||||
is_v1: false,
|
||||
}));
|
||||
}
|
||||
let entry_idx = self
|
||||
.entries
|
||||
.iter()
|
||||
.position(|e| e.name == name)
|
||||
.ok_or_else(|| anyhow::anyhow!("File {} not found in entries", name))?;
|
||||
let entry = &mut self.entries[entry_idx];
|
||||
entry.size = size.unwrap() as u32;
|
||||
entry.offset = self.writer.stream_position()?;
|
||||
entry.unpacked_size = entry.size;
|
||||
entry.is_packed = 0;
|
||||
entry.is_encrypted = 2;
|
||||
let common_key = self
|
||||
.common_key
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Common key is not available"))?;
|
||||
let hasher = Encryption31Hasher::new();
|
||||
let size = entry.size;
|
||||
let compute = EntryWriter {
|
||||
entry,
|
||||
inner: &mut self.writer,
|
||||
hasher,
|
||||
};
|
||||
let encryptor = Encryption31EncryptV2::new(
|
||||
compute,
|
||||
size,
|
||||
name.to_string(),
|
||||
self.key,
|
||||
common_key.to_vec(),
|
||||
)?;
|
||||
Ok(Box::new(encryptor))
|
||||
}
|
||||
|
||||
fn write_header(&mut self) -> Result<()> {
|
||||
self.header.index_offset = self.writer.stream_position()?;
|
||||
for entry in &self.entries {
|
||||
let name_length = entry.name.encode_utf16().count() as u16;
|
||||
self.writer.write_u16(name_length)?;
|
||||
let mut encoded = encode_string(Encoding::Utf16LE, &entry.name, true)?;
|
||||
self.encryption
|
||||
.encrypt_name(&mut encoded, self.key as i32)?;
|
||||
self.writer.write_all(&encoded)?;
|
||||
self.writer.write_u64(entry.offset)?;
|
||||
self.writer.write_u32(entry.size)?;
|
||||
self.writer.write_u32(entry.unpacked_size)?;
|
||||
self.writer.write_u32(entry.is_packed)?;
|
||||
self.writer.write_u32(entry.is_encrypted)?;
|
||||
self.writer.write_u32(entry.hash)?;
|
||||
}
|
||||
self.writer
|
||||
.write_struct(&self.hash, false, Encoding::Utf8, &None)?;
|
||||
self.writer
|
||||
.write_struct(&self.qkey, false, Encoding::Utf8, &None)?;
|
||||
self.writer
|
||||
.write_struct(&self.header, false, Encoding::Utf8, &None)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_mlist() {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
let t = Arc::new(AtomicI32::new(0));
|
||||
struct Test {
|
||||
value: i32,
|
||||
t: Arc<AtomicI32>,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn new(value: i32, t: Arc<AtomicI32>) -> Self {
|
||||
Self { value, t }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Test {
|
||||
fn drop(&mut self) {
|
||||
self.t.fetch_add(self.value, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut list: MList<Test> = MList::new();
|
||||
list.push(Test::new(1, t.clone()), true);
|
||||
list.push(Test::new(2, t.clone()), true);
|
||||
list.push(Test::new(3, t.clone()), true);
|
||||
}
|
||||
let v = t.load(Ordering::SeqCst);
|
||||
assert_eq!(v, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mlist() {
|
||||
let mut list = MList::new();
|
||||
list.push(1, true);
|
||||
list.push(2, true);
|
||||
list.push(3, true);
|
||||
assert_eq!(list.depth, 3);
|
||||
assert_eq!(list.pop(false), Some(1));
|
||||
assert_eq!(list.depth, 2);
|
||||
assert_eq!(list.pop(false), Some(2));
|
||||
assert_eq!(list.depth, 1);
|
||||
assert_eq!(list.pop(false), Some(3));
|
||||
assert_eq!(list.depth, 0);
|
||||
assert_eq!(list.pop(false), None);
|
||||
}
|
||||
Reference in New Issue
Block a user