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}