Skip to main content

banished_formats/
savegame.rs

1//! The game-state stream of a save: the game loop's serializer over every manager and every object.
2//! See docs/SAVE-GAME-STATE.md.
3//!
4//! Objects (`Object::Internal`) are written where they are first referenced
5//! (`InternalBasePtr::Serialize`): a present byte, the object's factory index, its tag and its type
6//! id; the first time an index appears its serializer follows inline
7//! (`InternalFactory::PersistObject`).
8//!
9//! Each type's serializer is described by a schema ([`crate::savegame_schema`]) of fields, blocks,
10//! lists and version conditions, read (and written) by one interpreter. Every block must be
11//! consumed exactly.
12//!
13//! Original: `FUN_14009af60`, Runtime `0x1800c29f0`, Runtime `0x1800c2780`.
14// vtable +0x18
15
16use std::collections::{BTreeMap, BTreeSet};
17
18use crate::crs::{Reader, Writer, BLOCK_END, BLOCK_START};
19use crate::error::{format_err, Error, Result};
20use crate::savegame_schema::{schema_for, Cond, Op, ACHIEVEMENTS, TOWN};
21
22// Internal +0x08
23/// An object's factory index (`Internal`).
24pub type ObjId = i32;
25
26/// A decoded value.
27#[derive(Debug, Clone, PartialEq)]
28pub enum Value {
29    /// A bool.
30    Bool(bool),
31    /// An `i8`.
32    I8(i8),
33    /// An `i16`.
34    I16(i16),
35    /// A `u16`.
36    U16(u16),
37    /// An `i32`.
38    I32(i32),
39    /// A `u32`.
40    U32(u32),
41    /// An `i64`.
42    I64(i64),
43    /// An `f32`.
44    F32(f32),
45    /// A list of `f32`.
46    F32s(Vec<f32>),
47    /// A list of `i64`.
48    I64s(Vec<i64>),
49    /// A list of `u32`.
50    U32s(Vec<u32>),
51    /// A string.
52    Str(String),
53    /// An external reference id (`u32::MAX` for none).
54    Ext(u32),
55    /// An internal pointer to an object (none for null).
56    Ptr(Option<ObjId>),
57    /// A list of records.
58    List(Vec<Fields>),
59    /// A nested block: its version and fields.
60    Block(u32, Fields),
61}
62
63/// Named values in serialization order.
64#[derive(Debug, Clone, Default, PartialEq)]
65pub struct Fields(pub Vec<(&'static str, Value)>);
66
67impl Fields {
68    /// A field of this block by name.
69    pub fn get(&self, name: &str) -> Option<&Value> {
70        self.0.iter().find(|(n, _)| *n == name).map(|(_, v)| v)
71    }
72
73    /// A field inside nested blocks: `path` is `"block.field"`.
74    pub fn at(&self, path: &str) -> Option<&Value> {
75        let mut fields = self;
76        let mut parts = path.split('.').peekable();
77        while let Some(p) = parts.next() {
78            let v = fields.get(p)?;
79            if parts.peek().is_none() {
80                return Some(v);
81            }
82            match v {
83                Value::Block(_, f) => fields = f,
84                _ => return None,
85            }
86        }
87        None
88    }
89
90    /// [`Fields::at`] for writing.
91    pub fn at_mut(&mut self, path: &str) -> Option<&mut Value> {
92        let (head, rest) = match path.split_once('.') {
93            Some((h, r)) => (h, Some(r)),
94            None => (path, None),
95        };
96        let v = self
97            .0
98            .iter_mut()
99            .find(|(n, _)| *n == head)
100            .map(|(_, v)| v)?;
101        match rest {
102            None => Some(v),
103            Some(rest) => match v {
104                Value::Block(_, f) => f.at_mut(rest),
105                _ => None,
106            },
107        }
108    }
109
110    /// Replace the value at `path`, keeping its kind (an `I32` stays an `I32`, a `Bool` that
111    /// holds a raw byte takes the new truth value as 0 or 1 only if it changed). Returns whether
112    /// the field exists.
113    pub fn set(&mut self, path: &str, value: Value) -> bool {
114        let Some(slot) = self.at_mut(path) else {
115            return false;
116        };
117        // A raw non-0/1 bool byte stays as written while its truth value is unchanged.
118        if let (Value::I8(b), Value::Bool(v)) = (&*slot, &value) {
119            if (*b != 0) == *v {
120                return true;
121            }
122        }
123        *slot = value;
124        true
125    }
126
127    /// Set an `i32` field inside nested blocks (`"block.field"`). Returns whether it exists.
128    pub fn set_i32(&mut self, path: &str, v: i32) -> bool {
129        match self.at_mut(path) {
130            Some(Value::I32(x)) => *x = v,
131            Some(Value::U32(x)) | Some(Value::Ext(x)) => *x = v as u32,
132            Some(Value::I16(x)) => *x = v as i16,
133            Some(Value::I8(x)) => *x = v as i8,
134            Some(Value::U16(x)) => *x = v as u16,
135            _ => return false,
136        }
137        true
138    }
139
140    /// Set an `f32` field inside nested blocks. Returns whether it exists.
141    pub fn set_f32(&mut self, path: &str, v: f32) -> bool {
142        match self.at_mut(path) {
143            Some(Value::F32(x)) => *x = v,
144            _ => return false,
145        }
146        true
147    }
148
149    /// Set a time field (microseconds) inside nested blocks. Returns whether it exists.
150    pub fn set_time(&mut self, path: &str, v: i64) -> bool {
151        match self.at_mut(path) {
152            Some(Value::I64(x)) => *x = v,
153            _ => return false,
154        }
155        true
156    }
157
158    /// Set a `bool` field inside nested blocks. Returns whether it exists.
159    pub fn set_bool(&mut self, path: &str, v: bool) -> bool {
160        self.set(path, Value::Bool(v))
161    }
162
163    /// An `i32` field inside nested blocks.
164    pub fn i32(&self, path: &str) -> Option<i32> {
165        match self.at(path)? {
166            Value::I32(v) => Some(*v),
167            Value::I16(v) => Some(*v as i32),
168            Value::I8(v) => Some(*v as i32),
169            Value::U32(v) | Value::Ext(v) => Some(*v as i32),
170            _ => None,
171        }
172    }
173
174    /// An `f32` field inside nested blocks.
175    pub fn f32(&self, path: &str) -> Option<f32> {
176        match self.at(path)? {
177            Value::F32(v) => Some(*v),
178            _ => None,
179        }
180    }
181
182    /// An object pointer field inside nested blocks.
183    pub fn ptr(&self, path: &str) -> Option<ObjId> {
184        match self.at(path)? {
185            Value::Ptr(p) => *p,
186            _ => None,
187        }
188    }
189}
190
191/// A decoded object.
192#[derive(Debug, Clone)]
193pub struct Object {
194    /// Its number in the stream.
195    pub index: ObjId,
196    // Internal +0x0c
197    /// `Internal`, drawn on the factory's own Random.
198    pub tag: u32,
199    /// The type's id.
200    pub type_id: u32,
201    /// The type's name.
202    pub type_name: &'static str,
203    /// The decoded fields.
204    pub fields: Fields,
205}
206
207/// The whole game state.
208#[derive(Debug, Clone, Default)]
209pub struct GameState {
210    /// The external reference table (`ExternalFactory::SerializeReferenceTable`).
211    pub references: BTreeMap<u32, String>,
212    // +0x40; +0x48; +0x30; +0x34; +0xd0; +0x15c; Game +0x50
213    /// The climate.
214    pub climate: u32,
215    /// The start condition.
216    pub start_condition: u32,
217    /// The terrain type.
218    pub terrain: u32,
219    /// The map seed.
220    pub seed: u32,
221    /// The map size.
222    pub size: i32,
223    /// The game's flags.
224    pub flags: u32,
225    /// The scenario.
226    pub scenario: i32,
227    /// The index of the game's Random (`System::Random::Serialize`).
228    pub random_index: u32,
229    /// The 624 words of the game's Random.
230    pub random_state: Vec<u32>,
231    /// The town's name.
232    pub town_name: String,
233    // +0x158, +0x150
234    /// The goal's state.
235    pub goal_done: bool,
236    /// The goal, if any.
237    pub goal: Option<ObjId>,
238    // +0xa8; +0xbc; +0x18; +0x1f; +0x1c; +0x20
239    /// The entity manager's clock.
240    ///
241    /// Original: `FUN_1400c9970`, `FUN_140022500`.
242    pub entity_time: i64,
243    /// The entities.
244    pub entities: Vec<Option<ObjId>>,
245    /// The scheduler's five bucket lists.
246    pub buckets: Vec<Vec<Option<ObjId>>>,
247    /// The scheduler's two five-int arrays.
248    pub bucket_values: [[i32; 5]; 2],
249    /// Each entity's schedule (the low 24 bits; the time).
250    pub schedules: Vec<(i32, i32, i32, i64)>,
251    /// The game loop serializer's version (3 in the samples).
252    pub version: u32,
253    /// The strings were written as UTF-16 (2015 builds) rather than 8-bit.
254    pub wide_strings: bool,
255    /// `InternalFactory::ResetPersistState`'s block, as written.
256    pub persist_state: Vec<u8>,
257    /// The entity manager block's version.
258    pub entity_manager_version: u32,
259    /// `AchievementManager::Persist`: the achievement instances.
260    pub achievements: Fields,
261    /// The animations: a whole block (markers included) carried through undecoded. An empty one
262    /// (a new game) is written as the engine's empty state ([`empty_animations`]).
263    pub animations: Vec<u8>,
264    /// The sound scene: a whole block carried through undecoded. An empty one is written as the engine's empty state ([`empty_audio_scene`]).
265    pub audio_scene: Vec<u8>,
266    /// The game camera.
267    ///
268    /// Original: `FUN_14009dd90`.
269    pub camera: Option<crate::menu_scene::SceneCamera>,
270    /// The terrain.
271    ///
272    /// Original: `FUN_1400b59a0`.
273    pub terrain_map: Option<crate::menu_scene::MapComponent>,
274    /// The town manager.
275    ///
276    /// Original: `FUN_140094c30`.
277    pub town: Fields,
278    /// The gameplay map.
279    ///
280    /// Original: `FUN_1400a45d0`.
281    pub map: GameMap,
282    /// The blocks after the map: `jobs`, `tools`, `calendar`, `event_log`, `environment` and
283    /// `minimap` (see `savegame_schema`).
284    pub managers: Fields,
285    /// Every object in the stream, by number.
286    pub objects: BTreeMap<ObjId, Object>,
287}
288
289/// One cell of the gameplay map, as the game state stores it (48 bytes in memory).
290///
291/// Original: `FUN_14001b570`.
292#[derive(Debug, Clone, Default, PartialEq)]
293pub struct GameMapCell {
294    // +0x00; +0x08
295    /// Not identified yet.
296    pub entities: [Option<ObjId>; 2],
297    // +0x10
298    /// The description at (reference id, `u32::MAX` when unset).
299    pub description: u32,
300    // +0x1c..+0x25
301    /// Bytes.
302    pub flags: [u8; 9],
303    // +0x26
304    /// Not identified yet.
305    pub value: u16,
306    // +0x29; +0x28..+0x2d
307    /// Bytes; `[1]` is the cell type.
308    pub bytes: [u8; 6],
309}
310
311// +0x90
312/// The gameplay map (version 4).
313///
314/// Original: `FUN_1400a45d0`.
315#[derive(Debug, Clone, Default)]
316pub struct GameMap {
317    /// The map's size in cells.
318    pub size: i32,
319    /// The `size × size` cells.
320    pub cells: Vec<GameMapCell>,
321    /// Nine shorts after the cells; not identified yet.
322    pub trailer: [i16; 9],
323}
324
325struct Decoder<'a> {
326    r: Reader<'a>,
327    objects: BTreeMap<ObjId, Object>,
328    defining: BTreeSet<ObjId>,
329}
330
331fn err<T>(at: usize, msg: impl Into<String>) -> Result<T> {
332    Err(Error::Format(format!("{} at {at:#x}", msg.into())))
333}
334
335impl Decoder<'_> {
336    fn ptr(&mut self) -> Result<Option<ObjId>> {
337        if !self.r.bool()? {
338            return Ok(None);
339        }
340        let at = self.r.pos();
341        let index = self.r.i32()?;
342        let tag = self.r.u32()?;
343        let ty = self.r.u32()?;
344        if self.objects.contains_key(&index) || self.defining.contains(&index) {
345            return Ok(Some(index));
346        }
347        let Some((name, ops)) = schema_for(ty) else {
348            return Err(Error::Unsupported(format!(
349                "object type {ty:#010x} (index {index}) at {at:#x}"
350            )));
351        };
352        self.defining.insert(index);
353        let fields = self.ops(ops, 0).map_err(|e| match e {
354            Error::Format(m) => Error::Format(format!("{m} [in {name} at {at:#x}]")),
355            Error::Truncated { at: t, wanted } => Error::Format(format!(
356                "truncated: wanted {wanted} bytes at {t:#x} [in {name} at {at:#x}]"
357            )),
358            e => e,
359        })?;
360        self.defining.remove(&index);
361        self.objects.insert(
362            index,
363            Object {
364                index,
365                tag,
366                type_id: ty,
367                type_name: name,
368                fields,
369            },
370        );
371        Ok(Some(index))
372    }
373
374    fn ptrs(&mut self) -> Result<Vec<Option<ObjId>>> {
375        let n = self.r.count()?;
376        (0..n).map(|_| self.ptr()).collect()
377    }
378
379    /// A block read by `f`, which must consume it.
380    fn block<T>(&mut self, f: impl FnOnce(&mut Self, u32) -> Result<T>) -> Result<T> {
381        let start = self.r.pos();
382        if self.r.u32()? != BLOCK_START {
383            return err(start, "expected a block");
384        }
385        let size = self.r.u32()? as usize;
386        let version = self.r.u32()?;
387        let end = self.r.pos() + size;
388        if size < 4 || self.r.u32_at(end - 4) != Some(BLOCK_END) {
389            return err(start, "block lacks its terminator");
390        }
391        let outer = self.r.limit(end - 4)?;
392        let result = f(self, version);
393        let unread = self.r.remaining();
394        self.r.unlimit(outer);
395        let value = result?;
396        if unread != 0 {
397            return err(
398                start,
399                format!("block (version {version}) has {unread} unread bytes"),
400            );
401        }
402        self.r.set_pos(end);
403        Ok(value)
404    }
405
406    /// `FUN_1400a45d0` with the cells of `FUN_14001b570`.
407    fn game_map(&mut self) -> Result<GameMap> {
408        self.block(|d, version| {
409            let size = d.r.i32()?;
410            let n = d.r.count()?;
411            let mut cells = Vec::with_capacity(n);
412            for _ in 0..n {
413                let entities = [d.ptr()?, d.ptr()?];
414                let description = d.r.u32()?;
415                let flags = d.r.bytes(9)?.try_into().unwrap();
416                let value = d.r.u16()?;
417                // Before version 3, another u16 (`FUN_14001b570`), not kept.
418                if version < 3 {
419                    d.r.u16()?;
420                }
421                let bytes = d.r.bytes(6)?.try_into().unwrap();
422                cells.push(GameMapCell {
423                    entities,
424                    description,
425                    flags,
426                    value,
427                    bytes,
428                });
429            }
430            let mut trailer = [0; 9];
431            for t in &mut trailer {
432                *t = d.r.i16()?;
433            }
434            Ok(GameMap {
435                size,
436                cells,
437                trailer,
438            })
439        })
440    }
441
442    /// One whole block's bytes, markers included.
443    fn raw_block(&mut self) -> Result<Vec<u8>> {
444        let start = self.r.pos();
445        self.skip_block()?;
446        Ok(self.r.slice(start, self.r.pos())?.to_vec())
447    }
448
449    fn skip_block(&mut self) -> Result<()> {
450        self.block(|d, _| {
451            d.r.skip_rest();
452            Ok(())
453        })
454    }
455
456    fn ops(&mut self, ops: &[Op], version: u32) -> Result<Fields> {
457        let mut out = Fields::default();
458        self.ops_into(ops, version, &mut out)?;
459        Ok(out)
460    }
461
462    fn ops_into(&mut self, ops: &[Op], version: u32, out: &mut Fields) -> Result<()> {
463        for op in ops {
464            let (name, value) = match *op {
465                // A byte other than 0 or 1 (an uninitialised bool) is kept as written.
466                Op::Bool(n) => match self.r.u8()? {
467                    b @ (0 | 1) => (n, Value::Bool(b == 1)),
468                    b => (n, Value::I8(b as i8)),
469                },
470                Op::I8(n) => (n, Value::I8(self.r.u8()? as i8)),
471                Op::I16(n) => (n, Value::I16(self.r.i16()?)),
472                Op::U16(n) => (n, Value::U16(self.r.u16()?)),
473                Op::I32(n) => (n, Value::I32(self.r.i32()?)),
474                Op::U32(n) => (n, Value::U32(self.r.u32()?)),
475                Op::Time(n) => (n, Value::I64(self.r.i64()?)),
476                Op::F32(n) => (n, Value::F32(self.r.f32()?)),
477                Op::Times(n, k) => (
478                    n,
479                    Value::I64s((0..k).map(|_| self.r.i64()).collect::<Result<_>>()?),
480                ),
481                Op::F32s(n, k) => (
482                    n,
483                    Value::F32s((0..k).map(|_| self.r.f32()).collect::<Result<_>>()?),
484                ),
485                Op::U32s(n, k) => (
486                    n,
487                    Value::U32s((0..k).map(|_| self.r.u32()).collect::<Result<_>>()?),
488                ),
489                Op::I32sBy(n, by) => {
490                    let k = out.i32(by).unwrap_or(0).max(0) as usize;
491                    (
492                        n,
493                        Value::U32s((0..k).map(|_| self.r.u32()).collect::<Result<_>>()?),
494                    )
495                }
496                Op::F32sBy(n, by) => {
497                    let k = out.i32(by).unwrap_or(0).max(0) as usize;
498                    (
499                        n,
500                        Value::F32s((0..k).map(|_| self.r.f32()).collect::<Result<_>>()?),
501                    )
502                }
503                Op::Str(n) => (n, Value::Str(self.r.string()?)),
504                Op::WStr(n) => (n, Value::Str(self.r.wstring()?)),
505                Op::Ext(n) => (n, Value::Ext(self.r.u32()?)),
506                Op::Ptr(n) => (n, Value::Ptr(self.ptr()?)),
507                Op::List(n, item) => {
508                    let count = self.r.count()?;
509                    let mut items = Vec::with_capacity(count.min(1 << 16));
510                    for _ in 0..count {
511                        items.push(self.ops(item, version)?);
512                    }
513                    (n, Value::List(items))
514                }
515                Op::Rest(n, item) => {
516                    let mut items = Vec::new();
517                    while self.r.remaining() > 0 {
518                        items.push(self.ops(item, version)?);
519                    }
520                    (n, Value::List(items))
521                }
522                Op::Block(n, _, inner) => {
523                    let (v, f) = self.block(|d, v| Ok((v, d.ops(inner, v)?)))?;
524                    (n, Value::Block(v, f))
525                }
526                Op::Sub(inner) => {
527                    self.ops_into(inner, version, out)?;
528                    continue;
529                }
530                Op::If(cond, inner) => {
531                    let take = match cond {
532                        Cond::VersionAtLeast(v) => version >= v,
533                        Cond::VersionBelow(v) => version < v,
534                        Cond::Flag(f) => truthy(out.get(f)),
535                    };
536                    if take {
537                        self.ops_into(inner, version, out)?;
538                    }
539                    continue;
540                }
541            };
542            out.0.push((name, value));
543        }
544        Ok(())
545    }
546}
547
548impl GameState {
549    // Internal +0x0c
550    /// A new object of type `name` with default fields ([`crate::savegame_schema::default_fields`])
551    /// and the next free factory index. The tag (`Internal`, drawn on the factory's own Random in
552    /// the original) is the index here; returns its id.
553    pub fn new_object(&mut self, name: &'static str) -> Option<ObjId> {
554        let fields = crate::savegame_schema::default_fields(name)?;
555        let index = self.objects.keys().next_back().map_or(1, |&k| k + 1);
556        self.objects.insert(
557            index,
558            Object {
559                index,
560                tag: index as u32,
561                type_id: crate::crc::type_id(name),
562                type_name: name,
563                fields,
564            },
565        );
566        Some(index)
567    }
568
569    /// Decode an expanded game-state stream (`crate::save::Save::state`).
570    pub fn decode(state: &[u8]) -> Result<GameState> {
571        let mut d = Decoder {
572            r: Reader::new(state),
573            objects: BTreeMap::new(),
574            defining: BTreeSet::new(),
575        };
576        let mut g = GameState::default();
577        d.block(|d, version| {
578            g.version = version;
579            g.references = d.block(|d, _| {
580                let n = d.r.count()?;
581                (0..n)
582                    .map(|_| Ok((d.r.u32()?, d.r.string()?)))
583                    .collect::<Result<BTreeMap<_, _>>>()
584            })?;
585            d.r.set_reference_ids(std::sync::Arc::new(g.references.clone()));
586            g.persist_state = d.raw_block()?;
587            g.climate = d.r.u32()?;
588            g.start_condition = d.r.u32()?;
589            g.terrain = d.r.u32()?;
590            g.seed = d.r.u32()?;
591            g.size = d.r.i32()?;
592            if version > 0 {
593                g.flags = d.r.u32()?;
594            }
595            if version > 2 {
596                g.scenario = d.r.i32()?;
597            }
598            g.random_index = d.r.u32()?;
599            g.random_state = (0..0x270).map(|_| d.r.u32()).collect::<Result<_>>()?;
600            g.town_name = d.r.string()?;
601            g.goal_done = d.r.bool()?;
602            g.goal = d.ptr()?;
603            // Kept as written: `AchievementManager::Persist`, `AnimationManager::Serialize` and
604            // `Audio::Scene::Serialize` (engine side, not yet decoded).
605            d.ops_into(&[ACHIEVEMENTS], 0, &mut g.achievements)?;
606            g.animations = d.raw_block()?;
607            g.audio_scene = d.raw_block()?;
608            g.camera = Some(crate::menu_scene::read_camera(&mut d.r)?);
609            d.block(|d, version| {
610                g.entity_manager_version = version;
611                g.entity_time = d.r.i64()?;
612                g.entities = d.ptrs()?;
613                g.buckets = (0..5).map(|_| d.ptrs()).collect::<Result<_>>()?;
614                for k in 0..2 {
615                    for v in 0..5 {
616                        g.bucket_values[k][v] = d.r.i32()?;
617                    }
618                }
619                g.schedules = (0..g.entities.len())
620                    .map(|_| Ok((d.r.i32()?, d.r.i32()?, d.r.i32()?, d.r.i64()?)))
621                    .collect::<Result<_>>()?;
622                Ok(())
623            })?;
624            g.terrain_map = Some(crate::menu_scene::read_map_component(&mut d.r)?);
625            d.ops_into(&[TOWN], 0, &mut g.town)?;
626            g.map = d.game_map()?;
627            d.ops_into(&managers(version), 0, &mut g.managers)?;
628            Ok(())
629        })?;
630        g.objects = d.objects;
631        g.wide_strings = d.r.wide_seen;
632        Ok(g)
633    }
634
635    /// The writing side of [`GameState::decode`]: the same stream, with each object written
636    /// inline where it is first referenced, in the order the decoder met them.
637    pub fn encode(&self) -> Result<Vec<u8>> {
638        let mut e = Encoder {
639            w: Writer::new(),
640            objects: &self.objects,
641            written: BTreeSet::new(),
642        };
643        e.w.wide_strings = self.wide_strings;
644        let at = e.w.begin_block(self.version);
645        let refs = e.w.begin_block(0);
646        e.w.count(self.references.len());
647        for (&id, path) in &self.references {
648            e.w.u32(id);
649            e.w.string(path);
650        }
651        e.w.end_block(refs);
652        e.w.set_reference_ids(&self.references);
653        // `InternalFactory::ResetPersistState`: the number of object slots, which every index
654        // must be below (raised when objects were added).
655        let slots = self
656            .objects
657            .keys()
658            .max()
659            .map_or(0, |&m| m as u32 + 1)
660            .max(persist_slots(&self.persist_state));
661        let at_persist = e.w.begin_block(0);
662        e.w.u32(slots);
663        e.w.end_block(at_persist);
664        e.w.u32(self.climate);
665        e.w.u32(self.start_condition);
666        e.w.u32(self.terrain);
667        e.w.u32(self.seed);
668        e.w.i32(self.size);
669        if self.version > 0 {
670            e.w.u32(self.flags);
671        }
672        if self.version > 2 {
673            e.w.i32(self.scenario);
674        }
675        e.w.u32(self.random_index);
676        self.random_state.iter().for_each(|&v| e.w.u32(v));
677        e.w.string(&self.town_name);
678        e.w.bool(self.goal_done);
679        e.ptr(self.goal)?;
680        e.ops(&[ACHIEVEMENTS], 0, &self.achievements)?;
681        if self.animations.is_empty() {
682            e.w.bytes(&empty_animations());
683        } else {
684            e.w.bytes(&self.animations);
685        }
686        if self.audio_scene.is_empty() {
687            e.w.bytes(&empty_audio_scene());
688        } else {
689            e.w.bytes(&self.audio_scene);
690        }
691        let camera = self
692            .camera
693            .as_ref()
694            .ok_or_else(|| Error::Format("no camera".into()))?;
695        crate::menu_scene::write_camera(&mut e.w, camera)?;
696        let em = e.w.begin_block(self.entity_manager_version);
697        e.w.i64(self.entity_time);
698        e.ptrs(&self.entities)?;
699        for b in &self.buckets {
700            e.ptrs(b)?;
701        }
702        self.bucket_values
703            .iter()
704            .flatten()
705            .for_each(|&v| e.w.i32(v));
706        for &(a, b, c, t) in &self.schedules {
707            e.w.i32(a);
708            e.w.i32(b);
709            e.w.i32(c);
710            e.w.i64(t);
711        }
712        e.w.end_block(em);
713        let terrain = self
714            .terrain_map
715            .as_ref()
716            .ok_or_else(|| Error::Format("no terrain".into()))?;
717        crate::menu_scene::write_map_component(&mut e.w, terrain)?;
718        e.ops(&[TOWN], 0, &self.town)?;
719        e.game_map(&self.map)?;
720        e.ops(&managers(self.version), 0, &self.managers)?;
721        e.w.end_block(at);
722        Ok(e.w.data)
723    }
724}
725
726/// A `Bool` field as the game reads it: any non-zero byte is true.
727fn truthy(v: Option<&Value>) -> bool {
728    matches!(v, Some(Value::Bool(true))) || matches!(v, Some(Value::I8(b)) if *b != 0)
729}
730
731/// The blocks after the map; the event log from game-loop version 2.
732/// The object slot count in a `ResetPersistState` block (marker, size, version, count).
733fn persist_slots(block: &[u8]) -> u32 {
734    block
735        .get(12..16)
736        .map_or(0, |b| u32::from_le_bytes(b.try_into().unwrap()))
737}
738
739/// `Graphics::AnimationManager::Serialize` with no animated instances: a count of 0.
740///
741/// Original: Runtime `0x1800fea20`.
742pub fn empty_animations() -> Vec<u8> {
743    let mut w = Writer::new();
744    let at = w.begin_block(0);
745    w.u32(0);
746    w.end_block(at);
747    w.data
748}
749
750// Each slot is written by Runtime 0x180053110; the five bytes are the scene's +0x30.
751/// The sound scene's block with nothing playing, as a new save writes it: 4,096 empty sound slots
752/// (a position, a value, the effect, a time, a value, the volume 1.0 and no points), then the five
753/// bytes that every sample save has.
754///
755/// Original: `Audio::Scene::Serialize`, Runtime `0x1800e28a0`.
756pub fn empty_audio_scene() -> Vec<u8> {
757    let mut w = Writer::new();
758    let at = w.begin_block(0);
759    w.u32(0x1000);
760    for _ in 0..0x1000 {
761        (0..3).for_each(|_| w.f32(0.0));
762        w.u32(0);
763        w.u32(u32::MAX);
764        w.i64(0);
765        w.u32(0);
766        w.f32(1.0);
767        w.u32(0);
768    }
769    w.bytes(&[0, 0, 1, 1, 1]);
770    w.end_block(at);
771    w.data
772}
773
774/// The blocks after the gameplay map (`jobs`, `tools`, `calendar`, `event_log`, `environment`,
775/// `minimap`) at serializer version `version`.
776pub fn managers(version: u32) -> Vec<Op> {
777    use crate::savegame_schema::{CALENDAR, ENVIRONMENT, EVENT_LOG, JOBS, MINIMAP, TOOLS};
778    let mut ops = vec![JOBS, TOOLS, CALENDAR];
779    if version > 1 {
780        ops.push(EVENT_LOG);
781    }
782    ops.extend([ENVIRONMENT, MINIMAP]);
783    ops
784}
785
786struct Encoder<'a> {
787    w: Writer,
788    objects: &'a BTreeMap<ObjId, Object>,
789    written: BTreeSet<ObjId>,
790}
791
792impl Encoder<'_> {
793    fn ptr(&mut self, p: Option<ObjId>) -> Result<()> {
794        let Some(index) = p else {
795            self.w.bool(false);
796            return Ok(());
797        };
798        let o = self
799            .objects
800            .get(&index)
801            .ok_or_else(|| Error::Format(format!("object {index} is not defined")))?;
802        self.w.bool(true);
803        self.w.i32(index);
804        self.w.u32(o.tag);
805        self.w.u32(o.type_id);
806        if self.written.insert(index) {
807            let (_, ops) = schema_for(o.type_id)
808                .ok_or_else(|| Error::Unsupported(format!("object type {:#010x}", o.type_id)))?;
809            self.ops(ops, 0, &o.fields)?;
810        }
811        Ok(())
812    }
813
814    fn ptrs(&mut self, ptrs: &[Option<ObjId>]) -> Result<()> {
815        self.w.count(ptrs.len());
816        ptrs.iter().try_for_each(|&p| self.ptr(p))
817    }
818
819    fn game_map(&mut self, map: &GameMap) -> Result<()> {
820        let at = self.w.begin_block(4);
821        self.w.i32(map.size);
822        self.w.count(map.cells.len());
823        for c in &map.cells {
824            self.ptr(c.entities[0])?;
825            self.ptr(c.entities[1])?;
826            self.w.u32(c.description);
827            self.w.bytes(&c.flags);
828            self.w.u16(c.value);
829            self.w.bytes(&c.bytes);
830        }
831        map.trailer.iter().for_each(|&t| self.w.i16(t));
832        self.w.end_block(at);
833        Ok(())
834    }
835
836    fn ops(&mut self, ops: &[Op], version: u32, fields: &Fields) -> Result<()> {
837        let mut next = fields.0.iter();
838        self.ops_from(ops, version, fields, &mut next)?;
839        match next.next() {
840            None => Ok(()),
841            Some((n, _)) => format_err(format!("field {n} has no step to write it")),
842        }
843    }
844
845    /// Write `ops` from the fields `next` yields (`fields` is the whole set, for conditions).
846    fn ops_from<'f>(
847        &mut self,
848        ops: &[Op],
849        version: u32,
850        fields: &'f Fields,
851        next: &mut std::slice::Iter<'f, (&'static str, Value)>,
852    ) -> Result<()> {
853        for op in ops {
854            let name = match *op {
855                Op::Sub(inner) => {
856                    self.ops_from(inner, version, fields, next)?;
857                    continue;
858                }
859                Op::If(cond, inner) => {
860                    let take = match cond {
861                        Cond::VersionAtLeast(v) => version >= v,
862                        Cond::VersionBelow(v) => version < v,
863                        Cond::Flag(f) => truthy(fields.get(f)),
864                    };
865                    if take {
866                        self.ops_from(inner, version, fields, next)?;
867                    }
868                    continue;
869                }
870                Op::Bool(n) | Op::I8(n) | Op::I16(n) | Op::U16(n) | Op::I32(n) | Op::U32(n) => n,
871                Op::Time(n) | Op::F32(n) | Op::Times(n, _) | Op::F32s(n, _) | Op::U32s(n, _) => n,
872                Op::I32sBy(n, _) | Op::F32sBy(n, _) | Op::Str(n) | Op::WStr(n) | Op::Ext(n) => n,
873                Op::Ptr(n) | Op::List(n, _) | Op::Rest(n, _) | Op::Block(n, _, _) => n,
874            };
875            let Some((field, value)) = next.next() else {
876                return format_err(format!("missing field {name}"));
877            };
878            if *field != name {
879                return format_err(format!("expected field {name}, found {field}"));
880            }
881            let w = &mut self.w;
882            match (*op, value) {
883                (Op::Bool(_), Value::Bool(v)) => w.bool(*v),
884                (Op::Bool(_), Value::I8(v)) => w.u8(*v as u8),
885                (Op::I8(_), Value::I8(v)) => w.u8(*v as u8),
886                (Op::I16(_), Value::I16(v)) => w.i16(*v),
887                (Op::U16(_), Value::U16(v)) => w.u16(*v),
888                (Op::I32(_), Value::I32(v)) => w.i32(*v),
889                (Op::U32(_), Value::U32(v)) | (Op::Ext(_), Value::Ext(v)) => w.u32(*v),
890                (Op::Time(_), Value::I64(v)) => w.i64(*v),
891                (Op::F32(_), Value::F32(v)) => w.f32(*v),
892                (Op::Times(_, _), Value::I64s(v)) => v.iter().for_each(|&t| w.i64(t)),
893                (Op::F32s(_, _) | Op::F32sBy(_, _), Value::F32s(v)) => {
894                    v.iter().for_each(|&f| w.f32(f))
895                }
896                (Op::U32s(_, _) | Op::I32sBy(_, _), Value::U32s(v)) => {
897                    v.iter().for_each(|&u| w.u32(u))
898                }
899                (Op::Str(_), Value::Str(v)) => w.string(v),
900                (Op::WStr(_), Value::Str(v)) => w.wstring(v),
901                (Op::Ptr(_), Value::Ptr(p)) => self.ptr(*p)?,
902                (Op::List(_, item), Value::List(items)) => {
903                    w.count(items.len());
904                    for f in items {
905                        self.ops(item, version, f)?;
906                    }
907                }
908                (Op::Rest(_, item), Value::List(items)) => {
909                    for f in items {
910                        self.ops(item, version, f)?;
911                    }
912                }
913                (Op::Block(_, _, inner), Value::Block(v, f)) => {
914                    let at = w.begin_block(*v);
915                    self.ops(inner, *v, f)?;
916                    self.w.end_block(at);
917                }
918                (op, value) => return format_err(format!("{op:?} cannot write {value:?}")),
919            }
920        }
921        Ok(())
922    }
923}