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 new() -> Self {
Self { ciphers: Vec::new() }
}
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) {
if 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) {
if 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 => {
let server_key = get_md5_key("battlecats");
encrypt_ecb(data, &server_key)
},
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) {
if let Ok(s) = String::from_utf8(bytes) { return Ok(s); }
}
let bc_key = get_md5_key("battlecats");
if let Ok(bytes) = decrypt_ecb(data, &bc_key) {
if let Ok(s) = String::from_utf8(bytes) { return Ok(s); }
}
Err(PackError::ListDecryptionFailed)
}
pub fn encrypt_list(data: &str) -> Result<Vec<u8>, PackError> {
let pack_key = get_md5_key("pack");
encrypt_ecb(data.as_bytes(), &pack_key)
}