imperator_save/
header.rs

1use std::io::Write;
2
3use crate::{ImperatorError, ImperatorErrorKind};
4
5/// A simplified and const generic version of arrayref
6#[inline]
7fn take<const N: usize>(data: &[u8]) -> [u8; N] {
8    debug_assert!(data.len() >= N);
9    unsafe { *(data.as_ptr() as *const [u8; N]) }
10}
11
12/// The kind of save file
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SaveHeaderKind {
15    /// uncompressed text
16    Text,
17
18    /// uncompressed binary
19    Binary,
20
21    /// uncompressed metadata header with compressed gamestate that includes
22    /// metadata (text)
23    UnifiedText,
24
25    /// uncompressed metadata header with compressed gamestate that includes
26    /// metadata (binary)
27    UnifiedBinary,
28
29    /// uncompressed metadata header with compressed gamestate that excludes
30    /// metadata (text)
31    SplitText,
32
33    /// uncompressed metadata header with compressed gamestate that excludes
34    /// metadata (binary)
35    SplitBinary,
36
37    /// An unknown type
38    Other(u16),
39}
40
41impl SaveHeaderKind {
42    pub fn new(kind: u16) -> SaveHeaderKind {
43        match kind {
44            0 => SaveHeaderKind::Text,
45            1 => SaveHeaderKind::Binary,
46            2 => SaveHeaderKind::UnifiedText,
47            3 => SaveHeaderKind::UnifiedBinary,
48            4 => SaveHeaderKind::SplitText,
49            5 => SaveHeaderKind::SplitBinary,
50            x => SaveHeaderKind::Other(x),
51        }
52    }
53
54    pub fn value(&self) -> u16 {
55        match self {
56            SaveHeaderKind::Text => 0,
57            SaveHeaderKind::Binary => 1,
58            SaveHeaderKind::UnifiedText => 2,
59            SaveHeaderKind::UnifiedBinary => 3,
60            SaveHeaderKind::SplitText => 4,
61            SaveHeaderKind::SplitBinary => 5,
62            SaveHeaderKind::Other(x) => *x,
63        }
64    }
65
66    pub fn is_binary(&self) -> bool {
67        matches!(
68            self,
69            SaveHeaderKind::Binary | SaveHeaderKind::UnifiedBinary | SaveHeaderKind::SplitBinary
70        )
71    }
72
73    pub fn is_text(&self) -> bool {
74        matches!(
75            self,
76            SaveHeaderKind::Text | SaveHeaderKind::UnifiedText | SaveHeaderKind::SplitText
77        )
78    }
79}
80
81/// The first line of the save file
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct SaveHeader {
84    unknown: [u8; 2],
85    kind: SaveHeaderKind,
86    random: [u8; 8],
87    meta_len: u64,
88    header_len: usize,
89}
90
91impl SaveHeader {
92    pub fn from_slice(data: &[u8]) -> Result<Self, ImperatorError> {
93        if data.len() < 24 {
94            return Err(ImperatorErrorKind::InvalidHeader.into());
95        }
96
97        if !matches!(&data[..3], [b'S', b'A', b'V']) {
98            return Err(ImperatorErrorKind::InvalidHeader.into());
99        }
100
101        let unknown = take::<2>(&data[3..5]);
102        let kind_hex =
103            std::str::from_utf8(&data[5..7]).map_err(|_| ImperatorErrorKind::InvalidHeader)?;
104        let kind =
105            u16::from_str_radix(kind_hex, 16).map_err(|_| ImperatorErrorKind::InvalidHeader)?;
106        let random = take::<8>(&data[7..15]);
107
108        let meta_hex =
109            std::str::from_utf8(&data[15..23]).map_err(|_| ImperatorErrorKind::InvalidHeader)?;
110        let meta_len =
111            u64::from_str_radix(meta_hex, 16).map_err(|_| ImperatorErrorKind::InvalidHeader)?;
112
113        let header_len = if data[23] == b'\r' && data.get(24) == Some(&b'\n') {
114            25
115        } else if data[23] == b'\n' {
116            24
117        } else {
118            return Err(ImperatorErrorKind::InvalidHeader.into());
119        };
120
121        Ok(SaveHeader {
122            unknown,
123            kind: SaveHeaderKind::new(kind),
124            random,
125            meta_len,
126            header_len,
127        })
128    }
129
130    pub fn kind(&self) -> SaveHeaderKind {
131        self.kind
132    }
133
134    pub fn set_kind(&mut self, kind: SaveHeaderKind) {
135        self.kind = kind;
136    }
137
138    pub fn header_len(&self) -> usize {
139        self.header_len
140    }
141
142    pub fn metadata_len(&self) -> u64 {
143        self.meta_len
144    }
145
146    pub fn set_metadata_len(&mut self, len: u64) {
147        self.meta_len = len
148    }
149
150    pub fn write<W>(&self, mut writer: W) -> std::io::Result<()>
151    where
152        W: Write,
153    {
154        writer.write_all(b"SAV")?;
155        writer.write_all(&self.unknown)?;
156        write!(writer, "{0:02x}", self.kind.value())?;
157        writer.write_all(&self.random)?;
158        write!(writer, "{0:08x}", self.meta_len)?;
159        if self.header_len() == 25 {
160            writer.write_all(b"\r")?;
161        }
162        writer.write_all(b"\n")?;
163        Ok(())
164    }
165}