use crate::pack::utils::ciphers::{
decrypt_cbc, decrypt_ecb,
encrypt_cbc, encrypt_ecb,
get_md5_key
};
pub use crate::pack::utils::verify::check_integrity;
use std::fmt;
use std::error::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackError {
InvalidHexFormat,
InvalidKeyLength,
MissingCipherParameters,
DecryptionFailed,
EncryptionFailed,
ListDecryptionFailed,
}
impl fmt::Display for PackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidHexFormat => write!(f, "Invalid hexadecimal format"),
Self::InvalidKeyLength => write!(f, "Decoded key or IV must be exactly 16 bytes"),
Self::MissingCipherParameters => write!(f, "Required key and IV parameters were not provided"),
Self::DecryptionFailed => write!(f, "AES decryption or padding validation failed"),
Self::EncryptionFailed => write!(f, "AES encryption or padding application failed"),
Self::ListDecryptionFailed => write!(f, "List manifest decryption failed (Invalid keys or corrupted file)"),
}
}
}
impl Error for PackError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Region { En, Jp, Kr, Tw }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackType { Standard, Server, ImageData }
#[derive(Clone)]
pub struct RegionalCipher {
pub region: Region,
pub key: [u8; 16],
pub iv: [u8; 16],
}
#[derive(Default)]
pub struct Keys {
pub ciphers: Vec<RegionalCipher>,
}
impl Keys {
pub fn parse(tuples: &[(Region, &str, &str)]) -> Result<Self, PackError> {
let mut ciphers = Vec::with_capacity(tuples.len());
for (region, hex_key, hex_iv) in tuples {
ciphers.push(Self::parse_cipher(*region, hex_key, hex_iv)?);
}
Ok(Self { ciphers })
}
fn parse_cipher(region: Region, hex_key: &str, hex_iv: &str) -> Result<RegionalCipher, PackError> {
let key_bytes = hex::decode(hex_key).map_err(|_| PackError::InvalidHexFormat)?;
let iv_bytes = hex::decode(hex_iv).map_err(|_| PackError::InvalidHexFormat)?;
let key: [u8; 16] = key_bytes.try_into().map_err(|_| PackError::InvalidKeyLength)?;
let iv: [u8; 16] = iv_bytes.try_into().map_err(|_| PackError::InvalidKeyLength)?;
Ok(RegionalCipher { region, key, iv })
}
}
pub fn decrypt_chunk(data: &[u8], internal_filename: &str, keys: &Keys) -> (Vec<u8>, Option<Region>) {
for cipher in &keys.ciphers {
if let Ok(result) = decrypt_cbc(data, &cipher.key, &cipher.iv)
&& check_integrity(&result, internal_filename) {
return (result, Some(cipher.region));
}
}
let server_key = get_md5_key("battlecats");
if let Ok(result) = decrypt_ecb(data, &server_key)
&& check_integrity(&result, internal_filename) {
return (result, None);
}
(data.to_vec(), None)
}
pub fn encrypt_chunk(data: &[u8], pack_type: PackType, key: Option<&[u8; 16]>, iv: Option<&[u8; 16]>) -> Result<Vec<u8>, PackError> {
match pack_type {
PackType::ImageData => Ok(data.to_vec()),
PackType::Server => encrypt_ecb(data, &get_md5_key("battlecats")),
PackType::Standard => {
let (Some(cipher_key), Some(cipher_iv)) = (key, iv) else {
return Err(PackError::MissingCipherParameters);
};
encrypt_cbc(data, cipher_key, cipher_iv)
}
}
}
pub fn decrypt_list(data: &[u8]) -> Result<String, PackError> {
let pack_key = get_md5_key("pack");
if let Ok(bytes) = decrypt_ecb(data, &pack_key)
&& let Ok(manifest_text) = String::from_utf8(bytes) { return Ok(manifest_text); }
let bc_key = get_md5_key("battlecats");
if let Ok(bytes) = decrypt_ecb(data, &bc_key)
&& let Ok(manifest_text) = String::from_utf8(bytes) { return Ok(manifest_text); }
Err(PackError::ListDecryptionFailed)
}
pub fn encrypt_list(data: &str) -> Result<Vec<u8>, PackError> {
encrypt_ecb(data.as_bytes(), &get_md5_key("pack"))
}