use std::fs;
use std::io;
use std::path::Path;
use crate::game_params::types::Param;
const MAGIC: [u8; 4] = *b"WUGP";
pub const FORMAT_VERSION: u32 = 12;
const HEADER_LEN: usize = MAGIC.len() + std::mem::size_of::<u32>();
pub fn encode(params: &[Param]) -> io::Result<Vec<u8>> {
let payload = rkyv::to_bytes::<rkyv::rancor::Error>(¶ms.to_vec())
.map_err(|e| io::Error::other(format!("rkyv serialize: {e}")))?;
let mut buf = Vec::with_capacity(HEADER_LEN + payload.len());
buf.extend_from_slice(&MAGIC);
buf.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
buf.extend_from_slice(&payload);
Ok(buf)
}
pub fn decode(bytes: &[u8]) -> Option<Vec<Param>> {
if bytes.len() < HEADER_LEN {
tracing::debug!("game-params cache rejected: byte sequence shorter than header");
return None;
}
if bytes[..MAGIC.len()] != MAGIC {
tracing::debug!("game-params cache rejected: missing WUGP magic (pre-versioned format)");
return None;
}
let version_bytes: [u8; 4] = bytes[MAGIC.len()..HEADER_LEN].try_into().ok()?;
let version = u32::from_le_bytes(version_bytes);
if version != FORMAT_VERSION {
tracing::debug!(
file_version = version,
current = FORMAT_VERSION,
"game-params cache rejected: format version mismatch"
);
return None;
}
let payload = &bytes[HEADER_LEN..];
rkyv::from_bytes::<Vec<Param>, rkyv::rancor::Error>(payload).ok()
}
pub fn load(path: &Path) -> Option<Vec<Param>> {
let bytes = fs::read(path).ok()?;
decode(&bytes)
}
pub fn save(path: &Path, params: &[Param]) -> io::Result<()> {
let buf = encode(params)?;
fs::write(path, &buf)
}
#[cfg(test)]
mod tests {
use super::FORMAT_VERSION;
#[test]
fn format_version_is_12() {
assert_eq!(FORMAT_VERSION, 12);
}
}