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
21pub struct MeltedDocument {
23 data: Vec<u8>,
24 unknown_tokens: HashSet<u16>,
25}
26
27impl MeltedDocument {
28 pub fn into_data(self) -> Vec<u8> {
30 self.data
31 }
32
33 pub fn data(&self) -> &[u8] {
35 self.data.as_slice()
36 }
37
38 pub fn unknown_tokens(&self) -> &HashSet<u16> {
40 &self.unknown_tokens
41 }
42}
43
44pub 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}