use crate::{
directory, reader, util, util::string_decryptor, util::version::PKGVersion, wz_image,
SharedWzStringDecryptor, WzDirectory, WzHeader, WzNodeArc, WzNodeArcVec, WzNodeCast,
WzObjectType, WzReader, WzSliceReader,
};
use memmap2::Mmap;
use std::fs::File;
use std::sync::{Arc, RwLock};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const WZ_VERSION_HEADER_64BIT_START: u16 = 770;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
FileError(#[from] std::io::Error),
#[error("invald wz file")]
InvalidWzFile,
#[error("Error with game version hash : The specified game version is incorrect and WzLib was unable to determine the version itself")]
ErrorGameVerHash,
#[error("Failed, in this case the causes are undetermined.")]
FailedUnknown,
#[error("Binary reading error")]
ReaderError(#[from] reader::Error),
#[error(transparent)]
DirectoryError(#[from] directory::Error),
#[error("[WzFile] New Wz image header found. checkByte = {0}, File Name = {1}")]
UnknownImageHeader(u8, String),
#[error("Unable to guess version")]
UnableToGuessVersion,
#[error("Unknown pkg version, can't resolve children")]
UnknownPkgVersion,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct WzFileMeta {
pub path: String,
pub patch_version: i32,
pub wz_version_header: i32,
pub wz_with_encrypt_version_header: bool,
pub hash: usize,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct WzFile {
#[cfg_attr(feature = "serde", serde(skip))]
pub reader: Arc<WzReader>,
#[cfg_attr(feature = "serde", serde(skip))]
pub offset: usize,
#[cfg_attr(feature = "serde", serde(skip))]
pub block_size: usize,
#[cfg_attr(feature = "serde", serde(skip))]
pub is_parsed: bool,
#[cfg_attr(feature = "serde", serde(flatten))]
pub wz_file_meta: WzFileMeta,
}
impl WzFile {
pub fn from_file<P>(
path: P,
wz_iv: Option<[u8; 4]>,
patch_version: Option<i32>,
existing_key: Option<&SharedWzStringDecryptor>,
) -> Result<WzFile, Error>
where
P: AsRef<std::path::Path>,
{
let file: File = File::open(&path)?;
let map = unsafe { Mmap::map(&file)? };
let block_size = map.len();
let existing_keys = existing_key.cloned().or_else(|| {
wz_iv.map(|iv| {
let keys: SharedWzStringDecryptor = Arc::new(RwLock::new(
string_decryptor::ecb_decryptor::EcbDecryptor::from_iv(iv),
));
keys
})
});
let verified_keys = existing_keys
.and_then(|keys| {
if string_decryptor::verify_decryptor_from_wz_file(&map, &keys).is_ok() {
Some(keys)
} else {
None
}
})
.or_else(|| string_decryptor::guess_decryptor_from_wz_file(&map));
let reader = if let Some(keys) = verified_keys {
WzReader::new(map).with_existing_keys(keys.clone())
} else {
WzReader::new(map)
};
let offset = WzHeader::read_data_start(&reader.map).map_err(|_| Error::InvalidWzFile)?;
let wz_file_meta = WzFileMeta {
path: path.as_ref().to_str().unwrap().to_string(),
patch_version: patch_version.unwrap_or(-1),
wz_version_header: 0,
wz_with_encrypt_version_header: true,
hash: 0,
};
Ok(WzFile {
offset: offset as usize,
block_size,
is_parsed: false,
reader: Arc::new(reader),
wz_file_meta,
})
}
pub fn parse(
&mut self,
parent: &WzNodeArc,
patch_version: Option<i32>,
) -> Result<WzNodeArcVec, Error> {
let reader = self.reader.clone();
let slice_reader = reader.create_slice_reader();
match slice_reader.header.ident {
PKGVersion::V1 => self.parse_pkg1(parent, patch_version, slice_reader),
PKGVersion::V2 => self.parse_pkg2(parent, slice_reader),
_ => Err(Error::UnknownPkgVersion),
}
}
fn parse_pkg1(
&mut self,
parent: &WzNodeArc,
patch_version: Option<i32>,
slice_reader: WzSliceReader,
) -> Result<WzNodeArcVec, Error> {
let option_encrypt_version = WzHeader::get_encrypted_version(
slice_reader.buf,
slice_reader.header.fstart,
slice_reader.header.fsize,
);
let mut wz_file_meta = WzFileMeta {
path: "".to_string(),
patch_version: patch_version.unwrap_or(self.wz_file_meta.patch_version),
wz_version_header: if let Some(encrypt_version) = option_encrypt_version {
encrypt_version as i32
} else {
WZ_VERSION_HEADER_64BIT_START as i32
},
wz_with_encrypt_version_header: option_encrypt_version.is_some(),
hash: 0,
};
let mut wz_dir = WzDirectory::new(self.offset, self.block_size, &self.reader, false);
let mut version_gen = util::version::pkg1::VersionGen::new(
wz_file_meta.wz_version_header,
wz_file_meta.patch_version,
2000,
);
if wz_file_meta.patch_version == -1 {
if wz_file_meta.wz_with_encrypt_version_header {
version_gen.current = 1;
version_gen.max_version = 2000;
} else {
version_gen.current = WZ_VERSION_HEADER_64BIT_START as i32;
version_gen.max_version = WZ_VERSION_HEADER_64BIT_START as i32 + 10;
};
for (ver_to_decode, hash) in version_gen {
wz_file_meta.hash = hash as usize;
wz_file_meta.wz_version_header = ver_to_decode;
wz_dir.hash = hash as usize;
if let Ok(children) =
self.try_decode_with_wz_version_number(parent, &wz_file_meta, &mut wz_dir)
{
wz_file_meta.patch_version = ver_to_decode;
self.update_wz_file_meta(wz_file_meta);
self.is_parsed = true;
return Ok(children);
}
}
return Err(Error::ErrorGameVerHash);
}
wz_file_meta.hash = version_gen.check_and_get_version_hash() as usize;
wz_dir.hash = wz_file_meta.hash;
let children =
self.try_decode_with_wz_version_number(parent, &wz_file_meta, &mut wz_dir)?;
self.update_wz_file_meta(wz_file_meta);
self.is_parsed = true;
Ok(children)
}
fn parse_pkg2(
&mut self,
parent: &WzNodeArc,
slice_reader: WzSliceReader,
) -> Result<WzNodeArcVec, Error> {
let [hash1, hash2] =
WzHeader::read_pkg2_hashes(slice_reader.buf, slice_reader.header.fstart)?;
let version_gen = util::version::pkg2::VersionGen::new(hash1, hash2);
let mut wz_file_meta = WzFileMeta {
path: "".to_string(),
patch_version: -1,
wz_version_header: 0,
wz_with_encrypt_version_header: false,
hash: 0,
};
let mut wz_dir = WzDirectory::new(self.offset, self.block_size, &self.reader, false);
for hash in version_gen {
wz_file_meta.hash = hash as usize;
wz_dir.hash = hash as usize;
if let Ok(children) =
self.try_decode_with_wz_version_number(parent, &wz_file_meta, &mut wz_dir)
{
self.update_wz_file_meta(wz_file_meta);
self.is_parsed = true;
return Ok(children);
}
}
Err(Error::ErrorGameVerHash)
}
fn try_decode_with_wz_version_number(
&self,
parent: &WzNodeArc,
meta: &WzFileMeta,
wz_dir: &mut WzDirectory,
) -> Result<WzNodeArcVec, Error> {
if meta.hash == 0 {
return Err(Error::ErrorGameVerHash);
}
let children = wz_dir.resolve_children(parent)?;
let first_image_node = children
.iter()
.find(|(_, node)| matches!(node.read().unwrap().object_type, WzObjectType::Image(_)));
if let Some((name, image_node)) = first_image_node {
let header_type = image_node
.read()
.unwrap()
.try_as_image()
.map(|node| node.get_header_type())
.ok_or(Error::ErrorGameVerHash)?;
if !wz_image::is_valid_image_header(header_type) {
return Err(Error::UnknownImageHeader(
header_type as u8,
name.to_string(),
));
}
}
if !meta.wz_with_encrypt_version_header && meta.wz_version_header == 113 {
return Err(Error::ErrorGameVerHash);
}
Ok(children)
}
fn update_wz_file_meta(&mut self, wz_file_meta: WzFileMeta) {
self.wz_file_meta = WzFileMeta {
path: std::mem::take(&mut self.wz_file_meta.path),
..wz_file_meta
};
}
}