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
35pub struct Hoi4File {}
39
40impl Hoi4File {
41 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 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#[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 }
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
292pub 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}