use crate::envelope::errors::{EnvelopeError, EnvelopeErrorKind};
use std::io::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SaveHeaderKind {
Text,
Binary,
UnifiedText,
UnifiedBinary,
SplitText,
SplitBinary,
Other(u16),
}
impl SaveHeaderKind {
pub fn new(kind: u16) -> SaveHeaderKind {
match kind {
0 => SaveHeaderKind::Text,
1 => SaveHeaderKind::Binary,
2 => SaveHeaderKind::UnifiedText,
3 => SaveHeaderKind::UnifiedBinary,
4 => SaveHeaderKind::SplitText,
5 => SaveHeaderKind::SplitBinary,
x => SaveHeaderKind::Other(x),
}
}
pub fn value(&self) -> u16 {
match self {
SaveHeaderKind::Text => 0,
SaveHeaderKind::Binary => 1,
SaveHeaderKind::UnifiedText => 2,
SaveHeaderKind::UnifiedBinary => 3,
SaveHeaderKind::SplitText => 4,
SaveHeaderKind::SplitBinary => 5,
SaveHeaderKind::Other(x) => *x,
}
}
pub fn is_binary(&self) -> bool {
matches!(
self,
SaveHeaderKind::Binary | SaveHeaderKind::UnifiedBinary | SaveHeaderKind::SplitBinary
)
}
pub fn is_text(&self) -> bool {
matches!(
self,
SaveHeaderKind::Text | SaveHeaderKind::UnifiedText | SaveHeaderKind::SplitText
)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SaveHeader {
version: u16,
kind: SaveHeaderKind,
random: [u8; 8],
meta_len: u64,
unknown: [u8; 8],
unknown_len: usize,
header_len: usize,
}
impl SaveHeader {
pub(crate) const SIZE: usize = 33;
pub fn from_slice(data: &[u8]) -> Result<Self, EnvelopeError> {
if data.len() < 24 {
return Err(EnvelopeErrorKind::InvalidHeader.into());
}
if !matches!(&data[..3], [b'S', b'A', b'V']) {
return Err(EnvelopeErrorKind::InvalidHeader.into());
}
let version_hex =
std::str::from_utf8(&data[3..5]).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let version =
u16::from_str_radix(version_hex, 16).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let kind_hex =
std::str::from_utf8(&data[5..7]).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let kind =
u16::from_str_radix(kind_hex, 16).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let random = data[7..15].try_into().unwrap();
let meta_hex =
std::str::from_utf8(&data[15..23]).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let meta_len =
u64::from_str_radix(meta_hex, 16).map_err(|_| EnvelopeErrorKind::InvalidHeader)?;
let search_data = &data[..Self::SIZE.min(data.len())];
let newline_pos = search_data
.iter()
.position(|&b| b == b'\n')
.ok_or(EnvelopeErrorKind::InvalidHeader)?;
let has_cr = newline_pos > 0 && data[newline_pos - 1] == b'\r';
let padding_end = newline_pos - (has_cr as usize);
let (unknown_len, unknown) = if padding_end == 23 {
(0, [0u8; 8])
} else if padding_end == 31 {
let mut pad = [0u8; 8];
pad.copy_from_slice(&data[23..31]);
(8, pad)
} else {
return Err(EnvelopeErrorKind::InvalidHeader.into());
};
let header_len = newline_pos + 1;
Ok(SaveHeader {
version,
kind: SaveHeaderKind::new(kind),
random,
meta_len,
unknown,
unknown_len,
header_len,
})
}
pub fn kind(&self) -> SaveHeaderKind {
self.kind
}
pub fn set_kind(&mut self, kind: SaveHeaderKind) {
self.kind = kind;
}
pub fn version(&self) -> u16 {
self.version
}
pub fn header_len(&self) -> usize {
self.header_len
}
pub fn metadata_len(&self) -> u64 {
self.meta_len
}
pub fn set_metadata_len(&mut self, len: u64) {
self.meta_len = len
}
pub fn write<W>(&self, mut writer: W) -> std::io::Result<()>
where
W: Write,
{
writer.write_all(b"SAV")?;
write!(writer, "{0:02x}", self.version)?;
write!(writer, "{0:02x}", self.kind.value())?;
writer.write_all(&self.random)?;
write!(writer, "{0:08x}", self.meta_len)?;
if self.unknown_len > 0 {
writer.write_all(&self.unknown[..self.unknown_len])?;
}
if self.header_len() == 25 || self.header_len() == 33 {
writer.write_all(b"\r")?;
}
writer.write_all(b"\n")?;
Ok(())
}
}
impl std::fmt::Display for SaveHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = Vec::new();
self.write(&mut buf).map_err(|_| std::fmt::Error)?;
let s = std::str::from_utf8(&buf).map_err(|_| std::fmt::Error)?;
f.write_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_save_header() {
let data = b"SAV0102a40f789f000067c4\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::UnifiedText);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 26564);
let mut out = Vec::new();
header.write(&mut out).unwrap();
assert_eq!(&out, data);
}
#[test]
fn test_save_header_allow_crlf() {
let data = b"SAV0102a40f789f000067c4\r\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::UnifiedText);
assert_eq!(header.header_len(), 25);
assert_eq!(header.metadata_len(), 26564);
let mut out = Vec::new();
header.write(&mut out).unwrap();
assert_eq!(&out, b"SAV0102a40f789f000067c4\r\n");
}
#[test]
fn test_split_vic3() {
let data = b"SAV010580b859da00000000\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::SplitBinary);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 0);
}
#[test]
fn test_debug_vic3() {
let data = b"SAV010078544999000003bc\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::Text);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 956);
}
#[test]
fn test_eu5_ironman_header() {
let data = b"SAV0103daabb23800062a40\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::UnifiedBinary);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 404032);
}
#[test]
fn test_eu5_debug_header() {
let data = b"SAV0100797de2430004dc53\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::Text);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 318547);
}
#[test]
fn test_ck3_binary_autosave_header() {
let data = b"SAV0101ad23696300004c29\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 1);
assert_eq!(header.kind(), SaveHeaderKind::Binary);
assert_eq!(header.header_len(), 24);
assert_eq!(header.metadata_len(), 19497);
}
#[test]
fn test_eu5_debug_header_2() {
let data = b"SAV020013f56aaf0004e72300000000\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 2);
assert_eq!(header.kind(), SaveHeaderKind::Text);
assert_eq!(header.header_len(), 32);
assert_eq!(header.metadata_len(), 0x4e723);
}
#[test]
fn test_eu5_debug_header_2_bin() {
let data = b"SAV0203fbf95f030006353800000000\n";
let header = SaveHeader::from_slice(&data[..]).unwrap();
assert_eq!(header.version(), 2);
assert_eq!(header.kind(), SaveHeaderKind::UnifiedBinary);
assert_eq!(header.header_len(), 32);
assert_eq!(header.metadata_len(), 0x63538);
}
}