Fix audio and movie unpack issue

This commit is contained in:
2025-11-02 19:35:55 +08:00
parent 04f9b1469a
commit 22544bef9d
2 changed files with 197 additions and 17 deletions

View File

@@ -6,7 +6,7 @@ use crate::utils::encoding::*;
use crate::utils::rc4::*;
use crate::utils::serde_base64bytes::Base64Bytes;
use crate::utils::struct_pack::*;
use crate::utils::xored_stream::XoredStream;
use crate::utils::xored_stream::*;
use anyhow::Result;
use msg_tool_macro::{StructPack, StructUnpack};
use serde::Deserialize;
@@ -34,11 +34,17 @@ struct Schema {
}
impl Schema {
pub fn get_type_key(&self, entry: &PazEntry) -> Option<&str> {
let name = std::path::Path::new(&entry.name)
pub fn get_type_key(&self, entry: &PazEntry, is_audio: bool) -> Option<&str> {
if is_audio {
return self.type_keys.get("ogg").map(|s| s.as_str());
}
let mut name = std::path::Path::new(&entry.name)
.extension()?
.to_string_lossy()
.to_lowercase();
if name == "mpg" || name == "mpeg" {
name = "avi".to_string();
}
self.type_keys.get(&name).map(|s| s.as_str())
}
}
@@ -179,8 +185,12 @@ pub struct PazArc {
entries: Vec<PazEntry>,
archive_encoding: Encoding,
xor_key: u8,
is_audio: bool,
mov_key: Option<Vec<u8>>,
}
const AUDIO_PAZ_NAMES: &[&str] = &["bgm", "se", "voice", "pmbgm", "pmse", "pmvoice"];
impl PazArc {
pub fn new<T: ReadSeek + 'static>(
reader: T,
@@ -228,6 +238,8 @@ impl PazArc {
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow::anyhow!("Invalid filename"))?
.to_lowercase();
let is_audio = AUDIO_PAZ_NAMES.contains(&arc_name.as_str());
let is_video = arc_name == "mov";
let arc_key = schema.arc_keys.get(&arc_name).ok_or_else(|| {
anyhow::anyhow!(
"No ARC key found for archive name '{}' in game schema",
@@ -246,6 +258,7 @@ impl PazArc {
if index_size & 7 != 0 {
return Err(anyhow::anyhow!("Invalid PAZ index size"));
}
let mut mov_key = None;
let entries = {
let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&arc_key.index_key)?;
let mut index_stream: Box<dyn ReadSeek> = Box::new(StreamRegion::new(
@@ -258,6 +271,17 @@ impl PazArc {
}
let mut index_stream = BlowfishDecryptor::new(blowfish.clone(), index_stream);
let count = index_stream.read_u32()?;
if is_video {
let mut key = index_stream.read_exact_vec(0x100)?;
if schema.version < 1 {
let mut nkey = vec![0u8; 0x100];
for i in 0..0x100 {
nkey[key[i] as usize] = i as u8;
}
key = nkey;
}
mov_key = Some(key);
}
let mut entries = Vec::with_capacity(count as usize);
for _ in 0..count {
let entry: PazEntry = index_stream.read_struct(false, archive_encoding)?;
@@ -272,6 +296,8 @@ impl PazArc {
entries,
archive_encoding,
xor_key,
is_audio,
mov_key,
})
}
}
@@ -321,23 +347,52 @@ impl Script for PazArc {
0,
entry.size as u64,
)?;
if let Some(type_key) = self.schema.get_type_key(&entry) {
let key = format!(
"{} {:08X} {}",
entry.name.to_ascii_lowercase(),
entry.unpacked_size,
type_key
);
let key = encode_string(self.archive_encoding, &key, false)?;
let mut rc4 = Rc4::new(&key);
if self.schema.version >= 2 {
let crc = crc32fast::hash(&key);
let skip = ((crc >> 12) as i32) & 0xFF;
rc4.skip_bytes(skip as usize);
if self.schema.version > 0 {
if let Some(type_key) = self.schema.get_type_key(&entry, self.is_audio) {
let key = format!(
"{} {:08X} {}",
entry.name.to_ascii_lowercase(),
entry.unpacked_size,
type_key
);
let key = encode_string(self.archive_encoding, &key, false)?;
let mut rc4 = Rc4::new(&key);
if self.schema.version >= 2 {
let crc = crc32fast::hash(&key);
let skip = ((crc >> 12) as i32) & 0xFF;
rc4.skip_bytes(skip as usize);
}
let stream = Rc4Stream::new(stream, rc4);
return Ok(Box::new(PazFileEntry::new(entry, stream)));
}
let stream = Rc4Stream::new(stream, rc4);
}
return Ok(Box::new(PazFileEntry::new(entry, stream)));
} else if let Some(mov_key) = &self.mov_key {
if self.schema.version < 1 {
let stream = TableEncryptedStream::new(stream, mov_key.clone())?;
return Ok(Box::new(PazFileEntry::new(entry, stream)));
}
let type_key = self
.schema
.get_type_key(&entry, self.is_audio)
.ok_or_else(|| {
anyhow::anyhow!("Data decryption key not found for entry '{}'.", entry.name)
})?;
let key = format!(
"{} {:08X} {}",
entry.name.to_ascii_lowercase(),
entry.unpacked_size,
type_key
);
let key = encode_string(self.archive_encoding, &key, false)?;
let mut rkey = mov_key.clone();
let key_len = key.len();
for i in 0..0x100 {
rkey[i] ^= key[i % key_len];
}
let mut rc4 = Rc4::new(&rkey);
let key_block = rc4.generate_block((entry.size as usize).min(0x10000));
let stream = XoredKeyStream::new(stream, key_block, 0);
return Ok(Box::new(PazFileEntry::new(entry, stream)));
}
Err(anyhow::anyhow!("Data decryption key not found."))
@@ -402,3 +457,58 @@ fn test_deserialize_paz() {
println!("Signature: {:08X}", schema.signature);
}
}
struct TableEncryptedStream<T> {
inner: T,
table: Vec<u8>,
}
impl<T> TableEncryptedStream<T> {
pub fn new(inner: T, table: Vec<u8>) -> Result<Self> {
if table.len() != 256 {
return Err(anyhow::anyhow!(
"Table length must be 256, got {}",
table.len()
));
}
Ok(TableEncryptedStream { inner, table })
}
}
impl<T: Read> Read for TableEncryptedStream<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let readed = self.inner.read(buf)?;
for i in 0..readed {
buf[i] = self.table[buf[i] as usize];
}
Ok(readed)
}
}
impl<T: Seek> Seek for TableEncryptedStream<T> {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.inner.rewind()
}
fn stream_position(&mut self) -> std::io::Result<u64> {
self.inner.stream_position()
}
}
impl<T: Write> Write for TableEncryptedStream<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut encrypted_buf = vec![0u8; buf.len()];
for i in 0..buf.len() {
encrypted_buf[i] = self.table[buf[i] as usize];
}
self.inner.write(&encrypted_buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

View File

@@ -57,3 +57,73 @@ impl<T: std::fmt::Debug> std::fmt::Debug for XoredStream<T> {
.finish()
}
}
/// A stream that XORs data with a repeating key based on the current position.
pub struct XoredKeyStream<T> {
inner: T,
key: Vec<u8>,
base_position: u64,
}
impl<T> XoredKeyStream<T> {
pub fn new(inner: T, key: Vec<u8>, base_position: u64) -> Self {
XoredKeyStream {
inner,
key,
base_position,
}
}
}
impl<T: Read + Seek> Read for XoredKeyStream<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let key_len = self.key.len();
let start_pos =
((self.inner.stream_position()? + self.base_position) % (key_len as u64)) as usize;
let readed = self.inner.read(buf)?;
for i in 0..readed {
buf[i] ^= self.key[(start_pos + i) % key_len];
}
Ok(readed)
}
}
impl<T: Seek> Seek for XoredKeyStream<T> {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.inner.rewind()
}
fn stream_position(&mut self) -> std::io::Result<u64> {
self.inner.stream_position()
}
}
impl<T: Write + Seek> Write for XoredKeyStream<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let key_len = self.key.len();
let start_pos =
((self.inner.stream_position()? + self.base_position) % (key_len as u64)) as usize;
let mut encrypted_buf = buf.to_vec();
for i in 0..buf.len() {
encrypted_buf[i] ^= self.key[(start_pos + i) % key_len];
}
self.inner.write(&encrypted_buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for XoredKeyStream<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XoredKeyStream")
.field("inner", &self.inner)
.field("base_position", &self.base_position)
.finish()
}
}