imperator_save/
melt.rs

1use crate::{
2    flavor::ImperatorFlavor, ImperatorDate, ImperatorError, ImperatorErrorKind, SaveHeader,
3    SaveHeaderKind,
4};
5use jomini::{
6    binary::{BinaryFlavor, FailedResolveStrategy, TokenResolver},
7    common::PdsDate,
8    BinaryTape, BinaryToken, TextWriterBuilder,
9};
10use std::collections::HashSet;
11
12#[derive(thiserror::Error, Debug)]
13pub(crate) enum MelterError {
14    #[error("{0}")]
15    Write(#[from] jomini::Error),
16
17    #[error("")]
18    UnknownToken { token_id: u16 },
19}
20
21/// Output from melting a binary save to plaintext
22pub struct MeltedDocument {
23    data: Vec<u8>,
24    unknown_tokens: HashSet<u16>,
25}
26
27impl MeltedDocument {
28    /// The converted plaintext data
29    pub fn into_data(self) -> Vec<u8> {
30        self.data
31    }
32
33    /// The converted plaintext data
34    pub fn data(&self) -> &[u8] {
35        self.data.as_slice()
36    }
37
38    /// The list of unknown tokens that the provided resolver accumulated
39    pub fn unknown_tokens(&self) -> &HashSet<u16> {
40        &self.unknown_tokens
41    }
42}
43
44/// Convert a binary save to plaintext
45pub struct ImperatorMelter<'a, 'b> {
46    tape: &'b BinaryTape<'a>,
47    header: &'b SaveHeader,
48    verbatim: bool,
49    on_failed_resolve: FailedResolveStrategy,
50}
51
52impl<'a, 'b> ImperatorMelter<'a, 'b> {
53    pub(crate) fn new(tape: &'b BinaryTape<'a>, header: &'b SaveHeader) -> Self {
54        ImperatorMelter {
55            tape,
56            header,
57            verbatim: false,
58            on_failed_resolve: FailedResolveStrategy::Ignore,
59        }
60    }
61
62    pub fn verbatim(&mut self, verbatim: bool) -> &mut Self {
63        self.verbatim = verbatim;
64        self
65    }
66
67    pub fn on_failed_resolve(&mut self, strategy: FailedResolveStrategy) -> &mut Self {
68        self.on_failed_resolve = strategy;
69        self
70    }
71
72    pub(crate) fn skip_value_idx(&self, token_idx: usize) -> usize {
73        self.tape
74            .tokens()
75            .get(token_idx + 1)
76            .map(|next_token| match next_token {
77                BinaryToken::Object(end) | BinaryToken::Array(end) => end + 1,
78                _ => token_idx + 2,
79            })
80            .unwrap_or(token_idx + 1)
81    }
82
83    pub fn melt<R>(&self, resolver: &R) -> Result<MeltedDocument, ImperatorError>
84    where
85        R: TokenResolver,
86    {
87        let out = melt(self, resolver).map_err(|e| match e {
88            MelterError::Write(x) => ImperatorErrorKind::Writer(x),
89            MelterError::UnknownToken { token_id } => ImperatorErrorKind::UnknownToken { token_id },
90        })?;
91        Ok(out)
92    }
93}
94
95fn update_header(data: &mut Vec<u8>, mut header: SaveHeader) {
96    header.set_kind(SaveHeaderKind::Text);
97    header.set_metadata_len((data.len() + 1 - header.header_len()) as u64);
98    let _ = header.write(&mut data[..header.header_len()]);
99}
100
101pub(crate) fn melt<R>(melter: &ImperatorMelter, resolver: &R) -> Result<MeltedDocument, MelterError>
102where
103    R: TokenResolver,
104{
105    let flavor = ImperatorFlavor;
106    let mut out = Vec::with_capacity(melter.tape.tokens().len() * 10);
107    let _ = melter.header.write(&mut out);
108
109    let mut unknown_tokens = HashSet::new();
110    let mut has_written_new_header = false;
111
112    let mut wtr = TextWriterBuilder::new()
113        .indent_char(b'\t')
114        .indent_factor(1)
115        .from_writer(out);
116    let mut token_idx = 0;
117    let mut known_number = false;
118    let tokens = melter.tape.tokens();
119
120    while let Some(token) = tokens.get(token_idx) {
121        match token {
122            BinaryToken::Object(_) => {
123                wtr.write_object_start()?;
124            }
125            BinaryToken::MixedContainer => {
126                wtr.start_mixed_mode();
127            }
128            BinaryToken::Equal => {
129                wtr.write_operator(jomini::text::Operator::Equal)?;
130            }
131            BinaryToken::Array(_) => {
132                wtr.write_array_start()?;
133            }
134            BinaryToken::End(_x) => {
135                wtr.write_end()?;
136            }
137            BinaryToken::Bool(x) => wtr.write_bool(*x)?,
138            BinaryToken::U32(x) => wtr.write_u32(*x)?,
139            BinaryToken::U64(x) => wtr.write_u64(*x)?,
140            BinaryToken::I32(x) => {
141                if known_number {
142                    wtr.write_i32(*x)?;
143                    known_number = false;
144                } else if let Some(date) = ImperatorDate::from_binary_heuristic(*x) {
145                    wtr.write_date(date.game_fmt())?;
146                } else {
147                    wtr.write_i32(*x)?;
148                }
149            }
150            BinaryToken::Quoted(x) => {
151                if wtr.expecting_key() {
152                    wtr.write_unquoted(x.as_bytes())?;
153                } else {
154                    wtr.write_quoted(x.as_bytes())?;
155                }
156            }
157            BinaryToken::Unquoted(x) => {
158                wtr.write_unquoted(x.as_bytes())?;
159            }
160            BinaryToken::F32(x) => wtr.write_f32(flavor.visit_f32(*x))?,
161            BinaryToken::F64(x) => wtr.write_f64(flavor.visit_f64(*x))?,
162            BinaryToken::Token(x) => match resolver.resolve(*x) {
163                Some(id) => {
164                    if !melter.verbatim && id == "is_ironman" && wtr.expecting_key() {
165                        token_idx = melter.skip_value_idx(token_idx);
166                        continue;
167                    }
168
169                    if id == "speed" {
170                        update_header(wtr.inner(), melter.header.clone());
171                        has_written_new_header = true;
172                    }
173
174                    known_number = id == "seed";
175                    wtr.write_unquoted(id.as_bytes())?;
176                }
177                None => match melter.on_failed_resolve {
178                    FailedResolveStrategy::Error => {
179                        return Err(MelterError::UnknownToken { token_id: *x });
180                    }
181                    FailedResolveStrategy::Ignore if wtr.expecting_key() => {
182                        token_idx = melter.skip_value_idx(token_idx);
183                        continue;
184                    }
185                    _ => {
186                        unknown_tokens.insert(*x);
187                        write!(wtr, "__unknown_0x{:x}", x)?;
188                    }
189                },
190            },
191            BinaryToken::Rgb(color) => {
192                wtr.write_header(b"rgb")?;
193                wtr.write_array_start()?;
194                wtr.write_u32(color.r)?;
195                wtr.write_u32(color.g)?;
196                wtr.write_u32(color.b)?;
197                wtr.write_end()?;
198            }
199        }
200
201        token_idx += 1;
202    }
203
204    let mut inner = wtr.into_inner();
205
206    if !has_written_new_header {
207        update_header(&mut inner, melter.header.clone());
208    }
209
210    inner.push(b'\n');
211
212    Ok(MeltedDocument {
213        data: inner,
214        unknown_tokens,
215    })
216}