hoi4save/
file.rs

1use std::{
2    collections::HashMap,
3    fs::File,
4    io::{self, Read, Write},
5};
6
7use crate::{
8    flavor::Hoi4Flavor, melt, models::Hoi4Save, Encoding, Hoi4Error, Hoi4ErrorKind, MeltOptions,
9    MeltedDocument,
10};
11use jomini::{binary::TokenResolver, text::ObjectReader, TextDeserializer, TextTape, Utf8Encoding};
12use serde::de::DeserializeOwned;
13
14const TXT_HEADER: &[u8] = b"HOI4txt";
15const BIN_HEADER: &[u8] = b"HOI4bin";
16
17enum FileHeader {
18    Text,
19    Binary,
20}
21
22fn file_header(data: &[u8]) -> Option<(FileHeader, &[u8])> {
23    if data.len() < TXT_HEADER.len() {
24        return None;
25    }
26
27    let (header, rest) = data.split_at(TXT_HEADER.len());
28    match header {
29        TXT_HEADER => Some((FileHeader::Text, rest)),
30        BIN_HEADER => Some((FileHeader::Binary, rest)),
31        _ => None,
32    }
33}
34
35/// Entrypoint for parsing HOI4 saves
36///
37/// Only consumes enough data to determine encoding of the file
38pub struct Hoi4File {}
39
40impl Hoi4File {
41    /// Parse a HOI4 file from a slice of data
42    pub fn from_slice(data: &[u8]) -> Result<Hoi4SliceFile, Hoi4Error> {
43        match file_header(data) {
44            Some((FileHeader::Text, data)) => Ok(Hoi4SliceFile {
45                kind: Hoi4SliceFileKind::Text(Hoi4Text(data)),
46            }),
47            Some((FileHeader::Binary, data)) => Ok(Hoi4SliceFile {
48                kind: Hoi4SliceFileKind::Binary(Hoi4Binary(data)),
49            }),
50            None => Err(Hoi4Error::new(Hoi4ErrorKind::UnknownHeader)),
51        }
52    }
53
54    /// Parse a HOI4 file from a file
55    pub fn from_file(mut file: File) -> Result<Hoi4FsFile, Hoi4Error> {
56        let mut header = [0u8; TXT_HEADER.len()];
57        file.read_exact(&mut header)?;
58        match file_header(&header) {
59            Some((FileHeader::Text, _)) => Ok(Hoi4FsFile {
60                kind: Hoi4FsFileKind::Text(Hoi4TextReader::from_reader(file)),
61            }),
62            Some((FileHeader::Binary, _)) => Ok(Hoi4FsFile {
63                kind: Hoi4FsFileKind::Binary(Hoi4Binary(file)),
64            }),
65            None => Err(Hoi4Error::new(Hoi4ErrorKind::UnknownHeader)),
66        }
67    }
68}
69
70#[derive(Debug, Clone)]
71pub enum Hoi4SliceFileKind<'a> {
72    Text(Hoi4Text<'a>),
73    Binary(Hoi4Binary<&'a [u8]>),
74}
75
76#[derive(Debug, Clone)]
77pub struct Hoi4SliceFile<'a> {
78    pub kind: Hoi4SliceFileKind<'a>,
79}
80
81impl<'a> Hoi4SliceFile<'a> {
82    pub fn kind(&self) -> &Hoi4SliceFileKind {
83        &self.kind
84    }
85
86    pub fn kind_mut(&'a mut self) -> &'a mut Hoi4SliceFileKind<'a> {
87        &mut self.kind
88    }
89
90    pub fn encoding(&self) -> Encoding {
91        match &self.kind {
92            Hoi4SliceFileKind::Text(_) => Encoding::Plaintext,
93            Hoi4SliceFileKind::Binary(_) => Encoding::Binary,
94        }
95    }
96
97    pub fn parse_save<R>(&self, resolver: R) -> Result<Hoi4Save, Hoi4Error>
98    where
99        R: TokenResolver,
100    {
101        self.parse(resolver)
102    }
103
104    pub fn parse<T, R>(&self, resolver: R) -> Result<T, Hoi4Error>
105    where
106        R: TokenResolver,
107        T: DeserializeOwned,
108    {
109        match &self.kind {
110            Hoi4SliceFileKind::Text(data) => data.deserializer().deserialize(),
111            Hoi4SliceFileKind::Binary(data) => data.clone().deserializer(resolver).deserialize(),
112        }
113    }
114
115    pub fn melt<Resolver, Writer>(
116        &self,
117        options: MeltOptions,
118        resolver: Resolver,
119        mut output: Writer,
120    ) -> Result<MeltedDocument, Hoi4Error>
121    where
122        Resolver: TokenResolver,
123        Writer: Write,
124    {
125        match &self.kind {
126            Hoi4SliceFileKind::Text(data) => {
127                output.write_all(TXT_HEADER)?;
128                output.write_all(
129                    b"
130",
131                )?;
132                output.write_all(data.0)?;
133                Ok(MeltedDocument::new())
134            }
135            Hoi4SliceFileKind::Binary(data) => {
136                output.write_all(TXT_HEADER)?;
137                output.write_all(
138                    b"
139",
140                )?;
141                let doc = melt::melt(data.0, &mut output, resolver, options)?;
142                output.write_all(
143                    b"
144",
145                )?;
146                Ok(doc)
147            }
148        }
149    }
150}
151
152pub enum Hoi4FsFileKind {
153    Text(Hoi4TextReader<File>),
154    Binary(Hoi4Binary<File>),
155}
156
157pub struct Hoi4FsFile {
158    pub kind: Hoi4FsFileKind,
159}
160
161impl Hoi4FsFile {
162    pub fn kind(&self) -> &Hoi4FsFileKind {
163        &self.kind
164    }
165
166    pub fn kind_mut(&mut self) -> &mut Hoi4FsFileKind {
167        &mut self.kind
168    }
169
170    pub fn encoding(&self) -> Encoding {
171        match &self.kind {
172            Hoi4FsFileKind::Text(_) => Encoding::Plaintext,
173            Hoi4FsFileKind::Binary(_) => Encoding::Binary,
174        }
175    }
176
177    pub fn parse_save<RES>(&mut self, resolver: RES) -> Result<Hoi4Save, Hoi4Error>
178    where
179        RES: TokenResolver,
180    {
181        match &mut self.kind {
182            Hoi4FsFileKind::Text(file) => file.as_ref().deserializer().deserialize(),
183            Hoi4FsFileKind::Binary(file) => file.as_ref().deserializer(resolver).deserialize(),
184        }
185    }
186
187    pub fn melt<Resolver, Writer>(
188        &mut self,
189        options: MeltOptions,
190        resolver: Resolver,
191        mut output: Writer,
192    ) -> Result<MeltedDocument, Hoi4Error>
193    where
194        Resolver: TokenResolver,
195        Writer: Write,
196    {
197        match &mut self.kind {
198            Hoi4FsFileKind::Text(file) => {
199                output.write_all(b"HOI4txt")?;
200                std::io::copy(&mut file.0, &mut output)?;
201                Ok(MeltedDocument::new())
202            }
203            Hoi4FsFileKind::Binary(file) => file.melt(options, resolver, output),
204        }
205    }
206}
207
208/// A Hoi4 text save
209#[derive(Debug, Clone)]
210pub struct Hoi4Text<'a>(&'a [u8]);
211
212impl<'a> Hoi4Text<'a> {
213    pub fn get_ref(&self) -> &'a [u8] {
214        self.0
215    }
216
217    pub fn deserializer(&self) -> Hoi4Modeller<'a, HashMap<u16, String>> {
218        Hoi4Modeller::from_reader(Box::new(self.0), HashMap::new(), Encoding::Plaintext)
219    }
220}
221
222#[derive(Debug)]
223pub struct Hoi4TextReader<R>(R);
224
225impl<R> Hoi4TextReader<R>
226where
227    R: Read,
228{
229    pub fn from_reader(reader: R) -> Self {
230        Self(reader)
231    }
232
233    pub fn as_ref(&self) -> Hoi4TextReader<&R> {
234        Hoi4TextReader(&self.0)
235    }
236
237    pub fn deserializer<'a>(self) -> Hoi4Modeller<'a, HashMap<u16, String>>
238    where
239        R: Read + 'a,
240    {
241        Hoi4Modeller::from_reader(self.0, HashMap::new(), Encoding::Plaintext)
242    }
243
244    // pub fn parse<T: DeserializeOwned>(&self) -> Result<T, Hoi4Error> {
245    //     self.deserializer().deserialize()
246    // }
247}
248
249impl<R: Read> Read for Hoi4TextReader<R> {
250    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
251        self.0.read(buf)
252    }
253}
254
255#[derive(Debug, Clone)]
256pub struct Hoi4Binary<R>(R);
257
258impl<R> Hoi4Binary<R>
259where
260    R: Read,
261{
262    pub fn get_ref(&self) -> &R {
263        &self.0
264    }
265
266    pub fn as_ref(&self) -> Hoi4Binary<&R> {
267        Hoi4Binary(&self.0)
268    }
269
270    pub fn deserializer<'a, Resolver>(self, resolver: Resolver) -> Hoi4Modeller<'a, Resolver>
271    where
272        R: Read + 'a,
273        Resolver: TokenResolver,
274    {
275        Hoi4Modeller::from_reader(self.0, resolver, Encoding::Binary)
276    }
277
278    pub fn melt<Resolver, Writer>(
279        &mut self,
280        options: MeltOptions,
281        resolver: Resolver,
282        mut output: Writer,
283    ) -> Result<MeltedDocument, Hoi4Error>
284    where
285        Resolver: TokenResolver,
286        Writer: Write,
287    {
288        melt::melt(&mut self.0, &mut output, resolver, options)
289    }
290}
291
292/// A parsed Hoi4 text document
293pub struct Hoi4ParsedText<'a> {
294    tape: TextTape<'a>,
295}
296
297impl<'a> Hoi4ParsedText<'a> {
298    pub fn from_slice(data: &'a [u8]) -> Result<Self, Hoi4Error> {
299        file_header(data)
300            .filter(|(header, _)| matches!(header, FileHeader::Text))
301            .map(|(_, data)| data)
302            .ok_or_else(|| Hoi4ErrorKind::UnknownHeader.into())
303            .and_then(Self::from_raw)
304    }
305
306    pub fn from_raw(data: &'a [u8]) -> Result<Self, Hoi4Error> {
307        let tape = TextTape::from_slice(data).map_err(Hoi4ErrorKind::Parse)?;
308        Ok(Hoi4ParsedText { tape })
309    }
310
311    pub fn reader(&self) -> ObjectReader<Utf8Encoding> {
312        self.tape.utf8_reader()
313    }
314}
315
316pub struct Hoi4Modeller<'obj, Resolver> {
317    reader: Box<dyn Read + 'obj>,
318    resolver: Resolver,
319    encoding: Encoding,
320}
321
322impl<'obj, Resolver: TokenResolver> Hoi4Modeller<'obj, Resolver> {
323    pub fn from_reader<Reader: Read + 'obj>(
324        reader: Reader,
325        resolver: Resolver,
326        encoding: Encoding,
327    ) -> Self {
328        Hoi4Modeller {
329            reader: Box::new(reader),
330            resolver,
331            encoding,
332        }
333    }
334
335    pub fn encoding(&self) -> Encoding {
336        self.encoding
337    }
338
339    pub fn deserialize<T>(&mut self) -> Result<T, Hoi4Error>
340    where
341        T: DeserializeOwned,
342    {
343        T::deserialize(self)
344    }
345}
346
347impl<'de, 'a: 'de, Resolver: TokenResolver> serde::de::Deserializer<'de>
348    for &'a mut Hoi4Modeller<'_, Resolver>
349{
350    type Error = Hoi4Error;
351
352    fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
353    where
354        V: serde::de::Visitor<'de>,
355    {
356        Err(Hoi4Error::new(Hoi4ErrorKind::DeserializeImpl {
357            msg: String::from("only struct supported"),
358        }))
359    }
360
361    fn deserialize_struct<V>(
362        self,
363        name: &'static str,
364        fields: &'static [&'static str],
365        visitor: V,
366    ) -> Result<V::Value, Self::Error>
367    where
368        V: serde::de::Visitor<'de>,
369    {
370        if matches!(self.encoding, Encoding::Binary) {
371            use jomini::binary::BinaryFlavor;
372            let flavor = Hoi4Flavor;
373            let mut deser = flavor
374                .deserializer()
375                .from_reader(&mut self.reader, &self.resolver);
376            Ok(deser.deserialize_struct(name, fields, visitor)?)
377        } else {
378            let reader = jomini::text::TokenReader::new(&mut self.reader);
379            let mut deser = TextDeserializer::from_utf8_reader(reader);
380            Ok(deser.deserialize_struct(name, fields, visitor)?)
381        }
382    }
383
384    serde::forward_to_deserialize_any! {
385        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
386        bytes byte_buf option unit unit_struct newtype_struct seq tuple
387        tuple_struct map enum identifier ignored_any
388    }
389}