use std::{
fmt::Write,
fs,
io::{Read, Write as IoWrite},
path::PathBuf,
};
use crate::core::{GDError, b64_encode};
use base64::{Engine, engine::general_purpose};
use flate2::{Compression, read::DeflateDecoder, write::ZlibEncoder};
use plist::{Dictionary, Value};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
fn zlib_compress(s: &str) -> Vec<u8> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(s.as_bytes()).unwrap();
encoder.finish().unwrap()
}
#[must_use]
pub fn encrypt_level_str(s: &str) -> Vec<u8> {
let compress = zlib_compress(s);
let mut data = b"H4sIAAAAAA".to_vec();
data.extend_from_slice(&compress[2..compress.len() - 4]);
let crc_checksum = crc32fast::hash(s.as_bytes()).to_le_bytes();
let size = s.len().to_le_bytes();
data.extend_from_slice(&crc_checksum);
data.extend_from_slice(&size);
let base64 = b64_encode(data);
let mut header = b"H4sIAAAAAAAAC".to_vec();
header.extend_from_slice(&base64.as_bytes()[13..]);
header
}
#[must_use]
pub fn encrypt_savefile_str(s: &str) -> Vec<u8> {
let mut encrypted = encrypt_level_str(s);
#[cfg(feature = "parallel")]
encrypted.par_iter_mut().for_each(|c| *c ^= 0x000b);
#[cfg(not(feature = "parallel"))]
for byte in &mut encrypted {
*byte ^= 0x000b;
}
encrypted
}
#[must_use]
pub fn stringify_xml(dict: &Dictionary, root: bool) -> String {
if dict.is_empty() {
return "<d />".to_owned();
};
let mut dict_str = String::with_capacity(4_096);
dict_str.push_str(match root {
true => "<dict>",
false => "<d>",
});
for (key, value) in dict {
let _ = write!(dict_str, "<k>{key}</k>");
match value {
Value::String(s) => {
let _ = write!(dict_str, "<s>{s}</s>");
}
Value::Integer(int) => {
let _ = write!(dict_str, "<i>{int}</i>");
}
Value::Dictionary(dict) => {
dict_str.push_str(&stringify_xml(dict, false));
}
Value::Boolean(b) => {
if *b {
dict_str.push_str("<t />");
} else {
dict_str.push_str("<f />");
}
}
Value::Real(float) => {
let _ = write!(dict_str, "<r>{float}</r>");
}
_ => {}
}
}
dict_str.push_str(match root {
true => "</dict>",
false => "</d>",
});
dict_str
}
pub fn decompress(mut data: Vec<u8>) -> Result<Vec<u8>, GDError> {
data.retain(|c| *c != 0);
let decoded = general_purpose::URL_SAFE.decode(data)?;
let sliced = &decoded[10..];
let mut decoder = DeflateDecoder::new(sliced);
let mut decompressed_buf = Vec::with_capacity(decoder.total_out() as usize);
decoder.read_to_end(&mut decompressed_buf)?;
Ok(decompressed_buf)
}
#[inline]
#[must_use]
pub fn decrypt(mut data: Vec<u8>) -> Result<Vec<u8>, GDError> {
#[cfg(feature = "parallel")]
data.par_iter_mut().for_each(|c| *c ^= 0x000b);
#[cfg(not(feature = "parallel"))]
for c in &mut data {
*c ^= 0x000b;
}
decompress(data)
}
pub fn decrypt_file(file: PathBuf) -> Result<String, GDError> {
let savefile = match fs::read(file) {
Ok(v) => v,
Err(e) => return Err(GDError::Io(e)),
};
let mut data = decrypt(savefile)?;
data.retain(|c| *c < 128);
Ok(String::from_utf8(data)?)
}
#[inline]
pub(crate) fn vec_as_str(data: &[u8]) -> String {
core::str::from_utf8(data).unwrap().to_string()
}