Skip to main content

rpfm_lib/files/esf/
mod.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! This module provides support for reading and writing ESF (Empire Save File) files.
12//!
13//! # Overview
14//!
15//! ESF files are hierarchical binary files used extensively in Total War games to store
16//! structured game data. They are found in various contexts including:
17//!
18//! - Campaign save games (`.save`, `.save_multiplayer`)
19//! - Campaign startup positions (startpos)
20//! - Character save files (`.twc`)
21//! - Composite Scene files (`.csc`)
22//! - Campaign Effect Object files (`.ccd`)
23//! - General ESF data (`.esf`)
24//!
25//! # File Structure
26//!
27//! An ESF file consists of:
28//!
29//! 1. **Header**: Contains the signature, unknown field, creation date, and offset to string tables.
30//! 2. **Node Tree**: A recursive tree structure starting from a root record node, containing
31//!    all the actual data organized hierarchically.
32//! 3. **String Tables**: Three separate tables at the end of the file:
33//!    - Record names: Names used by record nodes (e.g., "CAMPAIGN_SAVE_GAME", "FACTION")
34//!    - UTF-16 strings: String values referenced by UTF-16 string nodes
35//!    - UTF-8/ASCII strings: String values referenced by ASCII string nodes
36//!
37//! # Supported Signatures
38//!
39//! ESF files come in several format versions identified by their signature:
40//!
41//! | Signature | Status        | Notes                                   |
42//! |-----------|---------------|-----------------------------------------|
43//! | CAAB      | ✅ Supported  | Older format, uses u16 for string sizes |
44//! | CBAB      | ✅ Supported  | Newer format, uses u32 for string sizes |
45//! | CEAB      | ❌ Unsupported | Rare format                             |
46//! | CFAB      | ❌ Unsupported | Rare format                             |
47//!
48//! # Node Types
49//!
50//! The ESF format supports a rich set of node types organized into categories:
51//!
52//! ## Primitive Types
53//! - Boolean, signed/unsigned integers (8/16/32/64-bit), floats (32/64-bit)
54//! - 2D and 3D coordinates (pairs/triplets of f32)
55//! - UTF-16 and ASCII strings (stored as indices into string tables)
56//! - Angles (i16)
57//!
58//! ## Optimized Primitives
59//! Many primitive types have optimized encodings for common values to reduce file size:
60//! - `BOOL_TRUE`/`BOOL_FALSE`: Single byte instead of marker + value
61//! - `U32_ZERO`/`U32_ONE`: Single byte for 0 or 1
62//! - `U32_BYTE`/`U32_16BIT`/`U32_24BIT`: Smaller encodings when value fits
63//! - Similar optimizations exist for i32 and f32 (zero)
64//!
65//! ## Arrays
66//! All primitive types have corresponding array variants that store multiple values
67//! with a length prefix. Arrays also support optimized encodings.
68//!
69//! ## Record Nodes
70//! Record nodes are container nodes that hold other nodes. They have:
71//! - A name (from the record names table)
72//! - A version number
73//! - Flags controlling encoding behavior
74//! - Children organized into groups (for nested blocks) or a single list
75//!
76//! # Compression
77//!
78//! Large ESF files (particularly campaign startpos files) may contain compressed sections.
79//! Nodes with specific names (e.g., `CAMPAIGN_ENV`) are automatically compressed using LZMA1
80//! during encoding. The compressed data is stored in special `COMPRESSED_DATA` and
81//! `COMPRESSED_DATA_INFO` record nodes.
82//!
83//! During decoding, these compressed sections are automatically decompressed and the
84//! contained ESF data replaces the outer structure.
85//!
86//! # Usage
87//!
88//! ```ignore
89//! use rpfm_lib::files::esf::ESF;
90//! use rpfm_lib::files::{Decodeable, Encodeable};
91//! use std::io::Cursor;
92//!
93//! // Decode an ESF file
94//! let mut reader = Cursor::new(esf_bytes);
95//! let esf = ESF::decode(&mut reader, &None)?;
96//!
97//! // Access the root node and traverse the tree
98//! let root = esf.root_node();
99//!
100//! // Encode back to bytes
101//! let mut output = Vec::new();
102//! esf.encode(&mut output, &None)?;
103//! ```
104//!
105//! # Internal Submodules
106//!
107//! - `caab`: CAAB format-specific reading and writing logic
108//! - `cbab`: CBAB format-specific reading and writing logic
109//! - `utils`: Shared utilities for node reading/writing across formats
110
111use bitflags::bitflags;
112use getset::*;
113use serde_derive::{Serialize, Deserialize};
114
115use std::{fmt, fmt::Display};
116
117use crate::binary::{ReadBytes, WriteBytes};
118use crate::error::{Result, RLibError};
119use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
120
121/// File extensions associated with ESF files.
122///
123/// These extensions are used to identify files that should be parsed as ESF format.
124pub const EXTENSIONS: [&str; 6] = [
125    ".csc",                 // Composite Scene files
126    ".ccd",                 // CEO (Campaign Effect Object) files
127    ".esf",                 // Generic ESF data files
128    ".save",                // Single-player save game files
129    ".save_multiplayer",    // Multiplayer save game files
130    ".twc",                 // Total War Character save files
131];
132
133/// Magic bytes identifying the CAAB ESF format (older format with u16 string sizes).
134pub const SIGNATURE_CAAB: &[u8; 4] = &[0xCA, 0xAB, 0x00, 0x00];
135
136/// Magic bytes identifying the CBAB ESF format (newer format with u32 string sizes).
137pub const SIGNATURE_CBAB: &[u8; 4] = &[0xCB, 0xAB, 0x00, 0x00];
138
139/// Magic bytes identifying the CEAB ESF format (unsupported).
140pub const SIGNATURE_CEAB: &[u8; 4] = &[0xCE, 0xAB, 0x00, 0x00];
141
142/// Magic bytes identifying the CFAB ESF format (unsupported).
143pub const SIGNATURE_CFAB: &[u8; 4] = &[0xCF, 0xAB, 0x00, 0x00];
144
145mod caab;
146mod cbab;
147mod stream;
148mod utils;
149
150pub use self::utils::hash_map_order;
151
152//#[cfg(test)] mod esf_test;
153
154//---------------------------------------------------------------------------//
155//                              Node Type Markers
156//---------------------------------------------------------------------------//
157//
158// These byte markers identify the type of each node in the ESF binary format.
159// The marker system was originally documented in ESFEdit.
160//
161// Markers are organized into ranges:
162// - 0x00:        Invalid/reserved
163// - 0x01-0x10:   Primitive types (bool, integers, floats, strings, etc.)
164// - 0x12-0x1d:   Optimized primitives (compact encodings for common values)
165// - 0x21-0x26:   Unknown/undocumented types
166// - 0x41-0x50:   Arrays of primitive types
167// - 0x52-0x5d:   Arrays with optimized element encodings
168// - 0x80+:       Record nodes (high bit set indicates record type)
169
170/// Invalid marker - encountering this during parsing is always an error.
171pub const INVALID: u8 = 0x00;
172
173// Primitive type markers (0x01-0x10)
174const BOOL: u8 = 0x01;      // Boolean value (1 byte: 0 or 1)
175const I8: u8 = 0x02;        // Signed 8-bit integer
176const I16: u8 = 0x03;       // Signed 16-bit integer (little-endian)
177const I32: u8 = 0x04;       // Signed 32-bit integer (little-endian)
178const I64: u8 = 0x05;       // Signed 64-bit integer (little-endian)
179const U8: u8 = 0x06;        // Unsigned 8-bit integer
180const U16: u8 = 0x07;       // Unsigned 16-bit integer (little-endian)
181const U32: u8 = 0x08;       // Unsigned 32-bit integer (little-endian)
182const U64: u8 = 0x09;       // Unsigned 64-bit integer (little-endian)
183const F32: u8 = 0x0a;       // 32-bit floating point (IEEE 754)
184const F64: u8 = 0x0b;       // 64-bit floating point (IEEE 754)
185const COORD_2D: u8 = 0x0c;  // 2D coordinate (two f32 values: x, y)
186const COORD_3D: u8 = 0x0d;  // 3D coordinate (three f32 values: x, y, z)
187const UTF16: u8 = 0x0e;     // UTF-16 string (stored as index into string table)
188const ASCII: u8 = 0x0f;     // ASCII/UTF-8 string (stored as index into string table)
189const ANGLE: u8 = 0x10;     // Angle value (i16, likely representing degrees or radians scaled)
190
191// Optimized primitive markers (0x12-0x1d)
192// These provide compact encodings for common values to reduce file size.
193const BOOL_TRUE: u8 = 0x12;     // Boolean true (no additional bytes needed)
194const BOOL_FALSE: u8 = 0x13;    // Boolean false (no additional bytes needed)
195const U32_ZERO: u8 = 0x14;      // u32 value of 0 (no additional bytes)
196const U32_ONE: u8 = 0x15;       // u32 value of 1 (no additional bytes)
197const U32_BYTE: u8 = 0x16;      // u32 stored as single byte (0-255)
198const U32_16BIT: u8 = 0x17;     // u32 stored as 2 bytes (0-65535)
199const U32_24BIT: u8 = 0x18;     // u32 stored as 3 bytes (0-16777215)
200const I32_ZERO: u8 = 0x19;      // i32 value of 0 (no additional bytes)
201const I32_BYTE: u8 = 0x1a;      // i32 stored as single signed byte (-128 to 127)
202const I32_16BIT: u8 = 0x1b;     // i32 stored as 2 bytes (-32768 to 32767)
203const I32_24BIT: u8 = 0x1c;     // i32 stored as 3 bytes (-8388608 to 8388607)
204const F32_ZERO: u8 = 0x1d;      // f32 value of 0.0 (no additional bytes)
205
206// Unknown/undocumented type markers (0x21-0x26)
207// These types exist in some ESF files but their exact purpose is unclear.
208const UNKNOWN_21: u8 = 0x21;    // Unknown type, stores u32
209const UNKNOWN_23: u8 = 0x23;    // Unknown type, stores u8
210const UNKNOWN_24: u8 = 0x24;    // Unknown type, stores u16
211const UNKNOWN_25: u8 = 0x25;    // Unknown type, stores u32
212
213// Three Kingdoms DLC "Eight Princes" introduced this type
214const UNKNOWN_26: u8 = 0x26;    // Variable-length unknown type with special encoding
215
216// Array type markers (0x41-0x50)
217// Arrays store multiple values of the same type with a CAULEB128-encoded length prefix.
218const BOOL_ARRAY: u8 = 0x41;
219const I8_ARRAY: u8 = 0x42;
220const I16_ARRAY: u8 = 0x43;
221const I32_ARRAY: u8 = 0x44;
222const I64_ARRAY: u8 = 0x45;
223const U8_ARRAY: u8 = 0x46;
224const U16_ARRAY: u8 = 0x47;
225const U32_ARRAY: u8 = 0x48;
226const U64_ARRAY: u8 = 0x49;
227const F32_ARRAY: u8 = 0x4a;
228const F64_ARRAY: u8 = 0x4b;
229const COORD_2D_ARRAY: u8 = 0x4c;
230const COORD_3D_ARRAY: u8 = 0x4d;
231const UTF16_ARRAY: u8 = 0x4e;
232const ASCII_ARRAY: u8 = 0x4f;
233const ANGLE_ARRAY: u8 = 0x50;
234
235// Optimized array markers (0x52-0x5d)
236// Arrays where elements use compact encodings. Some markers exist in the format
237// but don't make practical sense (e.g., array of all zeros/ones).
238const BOOL_TRUE_ARRAY: u8 = 0x52;   // Unused - array of all true makes no sense
239const BOOL_FALSE_ARRAY: u8 = 0x53;  // Unused - array of all false makes no sense
240const U32_ZERO_ARRAY: u8 = 0x54;    // Unused - array of all zeros makes no sense
241const U32_ONE_ARRAY: u8 = 0x55;     // Unused - array of all ones makes no sense
242const U32_BYTE_ARRAY: u8 = 0x56;    // u32 array with each element stored as 1 byte
243const U32_16BIT_ARRAY: u8 = 0x57;   // u32 array with each element stored as 2 bytes
244const U32_24BIT_ARRAY: u8 = 0x58;   // u32 array with each element stored as 3 bytes
245const I32_ZERO_ARRAY: u8 = 0x59;    // Unused - array of all zeros makes no sense
246const I32_BYTE_ARRAY: u8 = 0x5a;    // i32 array with each element stored as 1 byte
247const I32_16BIT_ARRAY: u8 = 0x5b;   // i32 array with each element stored as 2 bytes
248const I32_24BIT_ARRAY: u8 = 0x5c;   // i32 array with each element stored as 3 bytes
249const F32_ZERO_ARRAY: u8 = 0x5d;    // Unused - array of all zeros makes no sense
250
251// Compression-related constants
252// Large nodes (e.g., campaign environment data) may be LZMA1-compressed.
253/// Record names that trigger automatic compression during encoding.
254const COMPRESSED_TAGS: [&str; 1] = ["CAMPAIGN_ENV"];
255/// Name of the record node containing compressed data bytes.
256const COMPRESSED_DATA_TAG: &str = "COMPRESSED_DATA";
257/// Name of the record node containing compression metadata (uncompressed size + LZMA header).
258const COMPRESSED_DATA_INFO_TAG: &str = "COMPRESSED_DATA_INFO";
259
260// Record nodes use the high bit of their type byte to indicate they are records,
261// with additional flag bits controlling encoding behavior.
262bitflags! {
263
264    /// Flags that control how a record node is encoded in the ESF binary format.
265    ///
266    /// These flags are stored in the high bits of the type byte for record nodes.
267    /// The combination of flags determines how the node header and children are encoded.
268    ///
269    /// # Binary Layout
270    ///
271    /// For a record node's first byte:
272    /// - Bit 7 (0x80): Always set for record nodes (`IS_RECORD_NODE`)
273    /// - Bit 6 (0x40): Set if node has nested blocks (`HAS_NESTED_BLOCKS`)
274    /// - Bit 5 (0x20): Set if using non-optimized header format (`HAS_NON_OPTIMIZED_INFO`)
275    /// - Bits 0-4: Used for optimized encoding of version/name when bit 5 is clear
276    #[derive(PartialEq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
277    pub struct RecordNodeFlags: u8 {
278
279        /// Indicates this is a record node (container with children).
280        /// This flag is always set for record nodes and distinguishes them from primitive nodes.
281        const IS_RECORD_NODE            = 0b1000_0000;
282
283        /// Indicates the record contains multiple groups of children (nested blocks).
284        /// When set, children are organized into separate groups, each with its own size prefix.
285        /// When clear, all children are in a single flat list.
286        const HAS_NESTED_BLOCKS         = 0b0100_0000;
287
288        /// Indicates the node uses the full 3-byte header format (u16 name index + u8 version).
289        /// When clear, the header is compressed into 2 bytes using bitwise encoding:
290        /// - Bits 1-4: Version (4 bits, max 15)
291        /// - Bit 0 + next byte: Name index (9 bits, max 511)
292        const HAS_NON_OPTIMIZED_INFO    = 0b0010_0000;
293    }
294}
295
296//---------------------------------------------------------------------------//
297//                              Enum & Structs
298//---------------------------------------------------------------------------//
299
300/// Represents a complete ESF file decoded in memory.
301///
302/// An ESF file contains a tree of nodes starting from a single root node. The root node
303/// is always a record node that contains all other data in the file.
304///
305/// # Fields
306///
307/// - `signature`: Identifies the format version (CAAB, CBAB, etc.)
308/// - `unknown_1`: Purpose unknown, typically 0
309/// - `creation_date`: Unix timestamp or similar date value
310/// - `root_node`: The top-level record node containing all file data
311#[derive(Getters, Setters, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
312#[getset(get = "pub", set = "pub")]
313pub struct ESF {
314
315    /// Format signature identifying the ESF version (CAAB, CBAB, etc.).
316    signature: ESFSignature,
317
318    /// Unknown header field, typically 0. May be reserved for future use.
319    unknown_1: u32,
320
321    /// Creation timestamp of the ESF file.
322    creation_date: u32,
323
324    /// Root node of the node tree containing all ESF data.
325    /// This is always a record node in valid ESF files.
326    root_node: NodeType,
327}
328
329/// Identifies the format version of an ESF file.
330///
331/// Different signatures indicate different encoding rules, particularly for
332/// string size prefixes and potentially other format variations.
333#[derive(Eq, PartialEq, Clone, Copy, Debug, Default, Serialize, Deserialize)]
334pub enum ESFSignature {
335    /// CAAB format - older version using u16 for string size prefixes.
336    #[default]
337    CAAB,
338    /// CBAB format - newer version using u32 for string size prefixes.
339    CBAB,
340    /// CEAB format - unsupported, rarely encountered.
341    CEAB,
342    /// CFAB format - unsupported, rarely encountered.
343    CFAB
344}
345
346/// Represents all possible node types in an ESF file.
347///
348/// ESF files use a tagged union approach where each node in the binary format
349/// starts with a type marker byte that identifies what kind of data follows.
350/// This enum mirrors that structure.
351///
352/// # Categories
353///
354/// - **Primitive nodes**: Single values (bool, integers, floats, strings)
355/// - **Optimized nodes**: Primitives with compact encoding tracking (`BoolNode`, `I32Node`, etc.)
356/// - **Complex nodes**: Structured data like coordinates
357/// - **Array nodes**: Collections of same-typed values
358/// - **Record nodes**: Container nodes with named children
359/// - **Unknown nodes**: Undocumented types preserved for round-trip fidelity
360///
361/// Note: Some type information was originally extracted from ESFEdit.
362#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
363pub enum NodeType {
364
365    /// Invalid/uninitialized node. Used as a placeholder; encountering this during
366    /// parsing or encoding indicates an error.
367    #[default]
368    Invalid,
369
370    // Primitive nodes
371    /// Boolean value with optimization tracking.
372    Bool(BoolNode),
373    /// Signed 8-bit integer.
374    I8(i8),
375    /// Signed 16-bit integer.
376    I16(i16),
377    /// Signed 32-bit integer with optimization tracking.
378    I32(I32Node),
379    /// Signed 64-bit integer.
380    I64(i64),
381    /// Unsigned 8-bit integer.
382    U8(u8),
383    /// Unsigned 16-bit integer.
384    U16(u16),
385    /// Unsigned 32-bit integer with optimization tracking.
386    U32(U32Node),
387    /// Unsigned 64-bit integer.
388    U64(u64),
389    /// 32-bit floating point with optimization tracking.
390    F32(F32Node),
391    /// 64-bit floating point.
392    F64(f64),
393    /// 2D coordinate (x, y as f32).
394    Coord2d(Coordinates2DNode),
395    /// 3D coordinate (x, y, z as f32).
396    Coord3d(Coordinates3DNode),
397    /// UTF-16 encoded string (stored as index, resolved during decode).
398    Utf16(String),
399    /// ASCII/UTF-8 encoded string (stored as index, resolved during decode).
400    Ascii(String),
401    /// Angle value stored as i16.
402    Angle(i16),
403
404    // Unknown/undocumented types - preserved for round-trip encoding fidelity
405    /// Unknown type 0x21 storing a u32 value.
406    Unknown21(u32),
407    /// Unknown type 0x23 storing a u8 value.
408    Unknown23(u8),
409    /// Unknown type 0x24 storing a u16 value.
410    Unknown24(u16),
411    /// Unknown type 0x25 storing a u32 value.
412    Unknown25(u32),
413    /// Unknown type 0x26 with variable-length data (Three Kingdoms DLC).
414    Unknown26(Vec<u8>),
415
416    // Array types
417    /// Array of boolean values.
418    BoolArray(Vec<bool>),
419    /// Array of i8 values (stored as u8 for efficiency).
420    I8Array(Vec<u8>),
421    /// Array of i16 values.
422    I16Array(Vec<i16>),
423    /// Array of i32 values with optimization tracking.
424    I32Array(VecI32Node),
425    /// Array of i64 values.
426    I64Array(Vec<i64>),
427    /// Array of u8 values (raw bytes).
428    U8Array(Vec<u8>),
429    /// Array of u16 values.
430    U16Array(Vec<u16>),
431    /// Array of u32 values with optimization tracking.
432    U32Array(VecU32Node),
433    /// Array of u64 values.
434    U64Array(Vec<u64>),
435    /// Array of f32 values.
436    F32Array(Vec<f32>),
437    /// Array of f64 values.
438    F64Array(Vec<f64>),
439    /// Array of 2D coordinates.
440    Coord2dArray(Vec<Coordinates2DNode>),
441    /// Array of 3D coordinates.
442    Coord3dArray(Vec<Coordinates3DNode>),
443    /// Array of UTF-16 strings.
444    Utf16Array(Vec<String>),
445    /// Array of ASCII strings.
446    AsciiArray(Vec<String>),
447    /// Array of angle values.
448    AngleArray(Vec<i16>),
449
450    /// Record node - a named container holding child nodes.
451    /// Records form the hierarchical structure of ESF files.
452    Record(Box<RecordNode>),
453}
454
455/// Boolean node with optimization tracking.
456///
457/// When `optimized` is true, the value is encoded using `BOOL_TRUE` (0x12) or
458/// `BOOL_FALSE` (0x13) markers which require no additional bytes. When false,
459/// uses the standard `BOOL` (0x01) marker followed by a byte.
460#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
461#[getset(get = "pub", get_mut = "pub", set = "pub")]
462pub struct BoolNode {
463    /// The boolean value.
464    value: bool,
465    /// Whether to use optimized encoding (single marker byte, no value byte).
466    optimized: bool,
467}
468
469/// Signed 32-bit integer node with optimization tracking.
470///
471/// When `optimized` is true, the encoder selects the smallest encoding that fits:
472/// - `I32_ZERO` (0x19): Value is 0, no additional bytes
473/// - `I32_BYTE` (0x1a): Value fits in i8 (-128 to 127), 1 byte
474/// - `I32_16BIT` (0x1b): Value fits in i16 (-32768 to 32767), 2 bytes
475/// - `I32_24BIT` (0x1c): Value fits in 24 bits, 3 bytes
476/// - `I32` (0x04): Full 4-byte encoding
477#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
478#[getset(get = "pub", get_mut = "pub", set = "pub")]
479pub struct I32Node {
480    /// The signed 32-bit integer value.
481    value: i32,
482    /// Whether to use optimized (variable-width) encoding.
483    optimized: bool,
484}
485
486/// Unsigned 32-bit integer node with optimization tracking.
487///
488/// When `optimized` is true, the encoder selects the smallest encoding that fits:
489/// - `U32_ZERO` (0x14): Value is 0, no additional bytes
490/// - `U32_ONE` (0x15): Value is 1, no additional bytes
491/// - `U32_BYTE` (0x16): Value fits in u8 (0-255), 1 byte
492/// - `U32_16BIT` (0x17): Value fits in u16 (0-65535), 2 bytes
493/// - `U32_24BIT` (0x18): Value fits in 24 bits (0-16777215), 3 bytes
494/// - `U32` (0x08): Full 4-byte encoding
495#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
496#[getset(get = "pub", get_mut = "pub", set = "pub")]
497pub struct U32Node {
498    /// The unsigned 32-bit integer value.
499    value: u32,
500    /// Whether to use optimized (variable-width) encoding.
501    optimized: bool,
502}
503
504/// 32-bit floating point node with optimization tracking.
505///
506/// When `optimized` is true and the value is exactly 0.0, uses `F32_ZERO` (0x1d)
507/// which requires no additional bytes. Otherwise uses standard `F32` (0x0a) encoding.
508#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
509#[getset(get = "pub", get_mut = "pub", set = "pub")]
510pub struct F32Node {
511    /// The 32-bit floating point value.
512    value: f32,
513    /// Whether to use optimized encoding (zero detection).
514    optimized: bool,
515}
516
517/// Array of signed 32-bit integers with optimization tracking.
518///
519/// When `optimized` is true, the encoder analyzes all values to find the smallest
520/// encoding that fits all elements, using byte/16-bit/24-bit arrays when possible.
521#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
522#[getset(get = "pub", get_mut = "pub", set = "pub")]
523pub struct VecI32Node {
524    /// The array of i32 values.
525    value: Vec<i32>,
526    /// Whether to use optimized (smaller element size) encoding when possible.
527    optimized: bool,
528}
529
530/// Array of unsigned 32-bit integers with optimization tracking.
531///
532/// When `optimized` is true, the encoder analyzes all values to find the smallest
533/// encoding that fits all elements, using byte/16-bit/24-bit arrays when possible.
534#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
535#[getset(get = "pub", get_mut = "pub", set = "pub")]
536pub struct VecU32Node {
537    /// The array of u32 values.
538    value: Vec<u32>,
539    /// Whether to use optimized (smaller element size) encoding when possible.
540    optimized: bool,
541}
542
543/// 2D coordinate node storing X and Y position values.
544///
545/// Commonly used for map positions, UI coordinates, and other 2D spatial data.
546/// Each coordinate is stored as a 32-bit float.
547#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
548#[getset(get = "pub", get_mut = "pub", set = "pub")]
549pub struct Coordinates2DNode {
550    /// X coordinate.
551    x: f32,
552    /// Y coordinate.
553    y: f32,
554}
555
556/// 3D coordinate node storing X, Y, and Z position values.
557///
558/// Commonly used for world positions, unit locations, and other 3D spatial data.
559/// Each coordinate is stored as a 32-bit float.
560#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
561#[getset(get = "pub", get_mut = "pub", set = "pub")]
562pub struct Coordinates3DNode {
563    /// X coordinate.
564    x: f32,
565    /// Y coordinate.
566    y: f32,
567    /// Z coordinate.
568    z: f32,
569}
570
571/// A record node that contains other nodes as children.
572///
573/// Record nodes are the structural backbone of ESF files, organizing data into
574/// a hierarchical tree. Each record has a name (referencing the string table),
575/// a version number, and zero or more child nodes.
576///
577/// # Children Organization
578///
579/// Children can be organized in two ways depending on the `HAS_NESTED_BLOCKS` flag:
580/// - **Without nested blocks**: Single flat list of children (`children[0]`)
581/// - **With nested blocks**: Multiple groups of children, each group representing
582///   a logical grouping (e.g., multiple instances of the same record type)
583///
584/// # Example Structure
585///
586/// A typical ESF might have a structure like:
587/// ```text
588/// ROOT
589/// ├── CAMPAIGN_SAVE_GAME (record)
590/// │   ├── FACTION (record, nested blocks for multiple factions)
591/// │   │   ├── [Group 0: Faction 1 data...]
592/// │   │   └── [Group 1: Faction 2 data...]
593/// │   └── DATE (record)
594/// │       ├── year (u32)
595/// │       └── turn (u32)
596/// ```
597#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
598#[getset(get = "pub", get_mut = "pub", set = "pub")]
599pub struct RecordNode {
600
601    /// Encoding flags controlling how this record is serialized.
602    record_flags: RecordNodeFlags,
603
604    /// Version number of this record's schema (0-255).
605    /// Used by the game to handle format changes across patches.
606    version: u8,
607
608    /// Name of the record (e.g., "FACTION", "CAMPAIGN_SAVE_GAME").
609    /// This is resolved from the record names string table during decode.
610    name: String,
611
612    /// Child nodes organized into groups.
613    /// - Without `HAS_NESTED_BLOCKS`: Single group at index 0
614    /// - With `HAS_NESTED_BLOCKS`: Multiple groups, each a separate logical unit
615    children: Vec<Vec<NodeType>>
616}
617
618//---------------------------------------------------------------------------//
619//                           Implementation of ESF
620//---------------------------------------------------------------------------//
621
622impl ESF {
623
624    /// Creates a shallow copy of this ESF with the root node replaced by `Invalid`.
625    ///
626    /// This is useful when you need to preserve the header metadata (signature,
627    /// creation date, etc.) but want to rebuild or replace the node tree.
628    pub fn clone_without_root_node(&self) -> Self {
629        Self {
630            signature: self.signature,
631            unknown_1: self.unknown_1,
632            creation_date: self.creation_date,
633            root_node: NodeType::Invalid,
634        }
635    }
636}
637
638impl NodeType {
639
640    /// Creates a copy of a node without its children (for record nodes).
641    ///
642    /// For record nodes, this creates a new record with the same name, flags,
643    /// and version but with an empty children list. For all other node types,
644    /// this is equivalent to `clone()`.
645    ///
646    /// Useful for building modified node trees while preserving the structure.
647    pub fn clone_without_children(&self) -> Self {
648        match self {
649            Self::Record(node) => {
650                let mut new_node = RecordNode::default();
651                new_node.set_name(node.name().to_owned());
652                new_node.set_record_flags(*node.record_flags());
653                new_node.set_version(*node.version());
654
655                Self::Record(Box::new(new_node))
656            }
657
658            _ => self.clone()
659        }
660    }
661}
662
663impl Display for ESFSignature {
664    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
665        Display::fmt(match self {
666            Self::CAAB => "CAAB",
667            Self::CBAB => "CBAB",
668            Self::CEAB => "CEAB",
669            Self::CFAB => "CFAB",
670        }, f)
671    }
672}
673
674impl TryFrom<&str> for ESFSignature {
675    type Error = RLibError;
676
677    fn try_from(value: &str) -> Result<Self, Self::Error> {
678        match value {
679            "CAAB" => Ok(Self::CAAB),
680            "CBAB" => Ok(Self::CBAB),
681            "CEAB" => Ok(Self::CEAB),
682            "CFAB" => Ok(Self::CFAB),
683            _ => Err(RLibError::UnknownESFSignature(value.to_string())),
684        }
685    }
686}
687
688impl TryFrom<Vec<u8>> for ESFSignature {
689    type Error = RLibError;
690
691    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
692        match value.as_slice().try_into()? {
693            SIGNATURE_CAAB => Ok(Self::CAAB),
694            SIGNATURE_CBAB => Ok(Self::CBAB),
695            SIGNATURE_CEAB => Ok(Self::CEAB),
696            SIGNATURE_CFAB => Ok(Self::CFAB),
697            _ => Err(RLibError::UnknownESFSignatureBytes(value[0], value[1])),
698        }
699    }
700}
701
702impl From<ESFSignature> for Vec<u8> {
703    fn from(value: ESFSignature) -> Self {
704        match value {
705            ESFSignature::CAAB => SIGNATURE_CAAB.to_vec(),
706            ESFSignature::CBAB => SIGNATURE_CBAB.to_vec(),
707            ESFSignature::CEAB => SIGNATURE_CEAB.to_vec(),
708            ESFSignature::CFAB => SIGNATURE_CFAB.to_vec(),
709        }
710    }
711}
712
713impl Decodeable for ESF {
714
715    fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
716        let mut esf = Self::default();
717
718        let sig_bytes = data.read_slice(4, false)?;
719        esf.signature = ESFSignature::try_from(sig_bytes.to_vec())?;
720
721        match esf.signature {
722            ESFSignature::CAAB => Self::read_caab(&mut esf, data)?,
723            ESFSignature::CBAB => Self::read_cbab(&mut esf, data)?,
724            _ => return Err(RLibError::DecodingESFUnsupportedSignature(sig_bytes[0], sig_bytes[1])),
725        };
726
727        // Debugging code.
728        //use std::io::Write;
729        //let mut x = std::fs::File::create("ceo.json")?;
730        //x.write_all(&serde_json::to_string_pretty(&esf).unwrap().as_bytes())?;
731
732        Ok(esf)
733    }
734}
735
736impl Encodeable for ESF {
737
738    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
739        let sig_bytes: Vec<u8> = Vec::from(self.signature);
740        buffer.write_all(&sig_bytes)?;
741
742        match self.signature {
743            ESFSignature::CAAB => self.save_caab(buffer, extra_data),
744            ESFSignature::CBAB => self.save_cbab(buffer, extra_data),
745            _ => Err(RLibError::EncodingESFUnsupportedSignature(self.signature.to_string())),
746        }
747    }
748}