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