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 /// If the string tables are written in index order (Rome 2's tools) instead of the order of the games' string hash maps.
329 /// Decoding sets it for a file written so.
330 #[serde(default)]
331 strings_in_index_order: bool,
332}
333
334/// Identifies the format version of an ESF file.
335///
336/// Different signatures indicate different encoding rules, particularly for
337/// string size prefixes and potentially other format variations.
338#[derive(Eq, PartialEq, Clone, Copy, Debug, Default, Serialize, Deserialize)]
339pub enum ESFSignature {
340 /// CAAB format - older version using u16 for string size prefixes.
341 #[default]
342 CAAB,
343 /// CBAB format - newer version using u32 for string size prefixes.
344 CBAB,
345 /// CEAB format - unsupported, rarely encountered.
346 CEAB,
347 /// CFAB format - unsupported, rarely encountered.
348 CFAB
349}
350
351/// Represents all possible node types in an ESF file.
352///
353/// ESF files use a tagged union approach where each node in the binary format
354/// starts with a type marker byte that identifies what kind of data follows.
355/// This enum mirrors that structure.
356///
357/// # Categories
358///
359/// - **Primitive nodes**: Single values (bool, integers, floats, strings)
360/// - **Optimized nodes**: Primitives with compact encoding tracking (`BoolNode`, `I32Node`, etc.)
361/// - **Complex nodes**: Structured data like coordinates
362/// - **Array nodes**: Collections of same-typed values
363/// - **Record nodes**: Container nodes with named children
364/// - **Unknown nodes**: Undocumented types preserved for round-trip fidelity
365///
366/// Note: Some type information was originally extracted from ESFEdit.
367#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
368pub enum NodeType {
369
370 /// Invalid/uninitialized node. Used as a placeholder; encountering this during
371 /// parsing or encoding indicates an error.
372 #[default]
373 Invalid,
374
375 // Primitive nodes
376 /// Boolean value with optimization tracking.
377 Bool(BoolNode),
378 /// Signed 8-bit integer.
379 I8(i8),
380 /// Signed 16-bit integer.
381 I16(i16),
382 /// Signed 32-bit integer with optimization tracking.
383 I32(I32Node),
384 /// Signed 64-bit integer.
385 I64(i64),
386 /// Unsigned 8-bit integer.
387 U8(u8),
388 /// Unsigned 16-bit integer.
389 U16(u16),
390 /// Unsigned 32-bit integer with optimization tracking.
391 U32(U32Node),
392 /// Unsigned 64-bit integer.
393 U64(u64),
394 /// 32-bit floating point with optimization tracking.
395 F32(F32Node),
396 /// 64-bit floating point.
397 F64(f64),
398 /// 2D coordinate (x, y as f32).
399 Coord2d(Coordinates2DNode),
400 /// 3D coordinate (x, y, z as f32).
401 Coord3d(Coordinates3DNode),
402 /// UTF-16 encoded string (stored as index, resolved during decode).
403 Utf16(String),
404 /// ASCII/UTF-8 encoded string (stored as index, resolved during decode).
405 Ascii(String),
406 /// Angle value stored as i16.
407 Angle(i16),
408
409 // Unknown/undocumented types - preserved for round-trip encoding fidelity
410 /// Unknown type 0x21 storing a u32 value.
411 Unknown21(u32),
412 /// Unknown type 0x23 storing a u8 value.
413 Unknown23(u8),
414 /// Unknown type 0x24 storing a u16 value.
415 Unknown24(u16),
416 /// Unknown type 0x25 storing a u32 value.
417 Unknown25(u32),
418 /// Unknown type 0x26 with variable-length data (Three Kingdoms DLC).
419 Unknown26(Vec<u8>),
420
421 // Array types
422 /// Array of boolean values.
423 BoolArray(Vec<bool>),
424 /// Array of i8 values (stored as u8 for efficiency).
425 I8Array(Vec<u8>),
426 /// Array of i16 values.
427 I16Array(Vec<i16>),
428 /// Array of i32 values with optimization tracking.
429 I32Array(VecI32Node),
430 /// Array of i64 values.
431 I64Array(Vec<i64>),
432 /// Array of u8 values (raw bytes).
433 U8Array(Vec<u8>),
434 /// Array of u16 values.
435 U16Array(Vec<u16>),
436 /// Array of u32 values with optimization tracking.
437 U32Array(VecU32Node),
438 /// Array of u64 values.
439 U64Array(Vec<u64>),
440 /// Array of f32 values.
441 F32Array(Vec<f32>),
442 /// Array of f64 values.
443 F64Array(Vec<f64>),
444 /// Array of 2D coordinates.
445 Coord2dArray(Vec<Coordinates2DNode>),
446 /// Array of 3D coordinates.
447 Coord3dArray(Vec<Coordinates3DNode>),
448 /// Array of UTF-16 strings.
449 Utf16Array(Vec<String>),
450 /// Array of ASCII strings.
451 AsciiArray(Vec<String>),
452 /// Array of angle values.
453 AngleArray(Vec<i16>),
454
455 /// Record node - a named container holding child nodes.
456 /// Records form the hierarchical structure of ESF files.
457 Record(Box<RecordNode>),
458}
459
460/// Boolean node with optimization tracking.
461///
462/// When `optimized` is true, the value is encoded using `BOOL_TRUE` (0x12) or
463/// `BOOL_FALSE` (0x13) markers which require no additional bytes. When false,
464/// uses the standard `BOOL` (0x01) marker followed by a byte.
465#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
466#[getset(get = "pub", get_mut = "pub", set = "pub")]
467pub struct BoolNode {
468 /// The boolean value.
469 value: bool,
470 /// Whether to use optimized encoding (single marker byte, no value byte).
471 optimized: bool,
472}
473
474/// Signed 32-bit integer node with optimization tracking.
475///
476/// When `optimized` is true, the encoder selects the smallest encoding that fits:
477/// - `I32_ZERO` (0x19): Value is 0, no additional bytes
478/// - `I32_BYTE` (0x1a): Value fits in i8 (-128 to 127), 1 byte
479/// - `I32_16BIT` (0x1b): Value fits in i16 (-32768 to 32767), 2 bytes
480/// - `I32_24BIT` (0x1c): Value fits in 24 bits, 3 bytes
481/// - `I32` (0x04): Full 4-byte encoding
482#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
483#[getset(get = "pub", get_mut = "pub", set = "pub")]
484pub struct I32Node {
485 /// The signed 32-bit integer value.
486 value: i32,
487 /// Whether to use optimized (variable-width) encoding.
488 optimized: bool,
489}
490
491/// Unsigned 32-bit integer node with optimization tracking.
492///
493/// When `optimized` is true, the encoder selects the smallest encoding that fits:
494/// - `U32_ZERO` (0x14): Value is 0, no additional bytes
495/// - `U32_ONE` (0x15): Value is 1, no additional bytes
496/// - `U32_BYTE` (0x16): Value fits in u8 (0-255), 1 byte
497/// - `U32_16BIT` (0x17): Value fits in u16 (0-65535), 2 bytes
498/// - `U32_24BIT` (0x18): Value fits in 24 bits (0-16777215), 3 bytes
499/// - `U32` (0x08): Full 4-byte encoding
500#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
501#[getset(get = "pub", get_mut = "pub", set = "pub")]
502pub struct U32Node {
503 /// The unsigned 32-bit integer value.
504 value: u32,
505 /// Whether to use optimized (variable-width) encoding.
506 optimized: bool,
507}
508
509/// 32-bit floating point node with optimization tracking.
510///
511/// When `optimized` is true and the value is exactly 0.0, uses `F32_ZERO` (0x1d)
512/// which requires no additional bytes. Otherwise uses standard `F32` (0x0a) encoding.
513#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
514#[getset(get = "pub", get_mut = "pub", set = "pub")]
515pub struct F32Node {
516 /// The 32-bit floating point value.
517 value: f32,
518 /// Whether to use optimized encoding (zero detection).
519 optimized: bool,
520}
521
522/// Array of signed 32-bit integers with optimization tracking.
523///
524/// When `optimized` is true, the encoder analyzes all values to find the smallest
525/// encoding that fits all elements, using byte/16-bit/24-bit arrays when possible.
526#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
527#[getset(get = "pub", get_mut = "pub", set = "pub")]
528pub struct VecI32Node {
529 /// The array of i32 values.
530 value: Vec<i32>,
531 /// Whether to use optimized (smaller element size) encoding when possible.
532 optimized: bool,
533}
534
535/// Array of unsigned 32-bit integers with optimization tracking.
536///
537/// When `optimized` is true, the encoder analyzes all values to find the smallest
538/// encoding that fits all elements, using byte/16-bit/24-bit arrays when possible.
539#[derive(Getters, MutGetters, Setters, Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
540#[getset(get = "pub", get_mut = "pub", set = "pub")]
541pub struct VecU32Node {
542 /// The array of u32 values.
543 value: Vec<u32>,
544 /// Whether to use optimized (smaller element size) encoding when possible.
545 optimized: bool,
546}
547
548/// 2D coordinate node storing X and Y position values.
549///
550/// Commonly used for map positions, UI coordinates, and other 2D spatial data.
551/// Each coordinate is stored as a 32-bit float.
552#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
553#[getset(get = "pub", get_mut = "pub", set = "pub")]
554pub struct Coordinates2DNode {
555 /// X coordinate.
556 x: f32,
557 /// Y coordinate.
558 y: f32,
559}
560
561/// 3D coordinate node storing X, Y, and Z position values.
562///
563/// Commonly used for world positions, unit locations, and other 3D spatial data.
564/// Each coordinate is stored as a 32-bit float.
565#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
566#[getset(get = "pub", get_mut = "pub", set = "pub")]
567pub struct Coordinates3DNode {
568 /// X coordinate.
569 x: f32,
570 /// Y coordinate.
571 y: f32,
572 /// Z coordinate.
573 z: f32,
574}
575
576/// A record node that contains other nodes as children.
577///
578/// Record nodes are the structural backbone of ESF files, organizing data into
579/// a hierarchical tree. Each record has a name (referencing the string table),
580/// a version number, and zero or more child nodes.
581///
582/// # Children Organization
583///
584/// Children can be organized in two ways depending on the `HAS_NESTED_BLOCKS` flag:
585/// - **Without nested blocks**: Single flat list of children (`children[0]`)
586/// - **With nested blocks**: Multiple groups of children, each group representing
587/// a logical grouping (e.g., multiple instances of the same record type)
588///
589/// # Example Structure
590///
591/// A typical ESF might have a structure like:
592/// ```text
593/// ROOT
594/// ├── CAMPAIGN_SAVE_GAME (record)
595/// │ ├── FACTION (record, nested blocks for multiple factions)
596/// │ │ ├── [Group 0: Faction 1 data...]
597/// │ │ └── [Group 1: Faction 2 data...]
598/// │ └── DATE (record)
599/// │ ├── year (u32)
600/// │ └── turn (u32)
601/// ```
602#[derive(Getters, MutGetters, Setters, PartialEq, Clone, Default, Debug, Serialize, Deserialize)]
603#[getset(get = "pub", get_mut = "pub", set = "pub")]
604pub struct RecordNode {
605
606 /// Encoding flags controlling how this record is serialized.
607 record_flags: RecordNodeFlags,
608
609 /// Version number of this record's schema (0-255).
610 /// Used by the game to handle format changes across patches.
611 version: u8,
612
613 /// Name of the record (e.g., "FACTION", "CAMPAIGN_SAVE_GAME").
614 /// This is resolved from the record names string table during decode.
615 name: String,
616
617 /// Child nodes organized into groups.
618 /// - Without `HAS_NESTED_BLOCKS`: Single group at index 0
619 /// - With `HAS_NESTED_BLOCKS`: Multiple groups, each a separate logical unit
620 children: Vec<Vec<NodeType>>
621}
622
623//---------------------------------------------------------------------------//
624// Implementation of ESF
625//---------------------------------------------------------------------------//
626
627impl ESF {
628
629 /// Creates a shallow copy of this ESF with the root node replaced by `Invalid`.
630 ///
631 /// This is useful when you need to preserve the header metadata (signature,
632 /// creation date, etc.) but want to rebuild or replace the node tree.
633 pub fn clone_without_root_node(&self) -> Self {
634 Self {
635 signature: self.signature,
636 unknown_1: self.unknown_1,
637 creation_date: self.creation_date,
638 root_node: NodeType::Invalid,
639 strings_in_index_order: self.strings_in_index_order,
640 }
641 }
642}
643
644impl NodeType {
645
646 /// Creates a copy of a node without its children (for record nodes).
647 ///
648 /// For record nodes, this creates a new record with the same name, flags,
649 /// and version but with an empty children list. For all other node types,
650 /// this is equivalent to `clone()`.
651 ///
652 /// Useful for building modified node trees while preserving the structure.
653 pub fn clone_without_children(&self) -> Self {
654 match self {
655 Self::Record(node) => {
656 let mut new_node = RecordNode::default();
657 new_node.set_name(node.name().to_owned());
658 new_node.set_record_flags(*node.record_flags());
659 new_node.set_version(*node.version());
660
661 Self::Record(Box::new(new_node))
662 }
663
664 _ => self.clone()
665 }
666 }
667}
668
669impl Display for ESFSignature {
670 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671 Display::fmt(match self {
672 Self::CAAB => "CAAB",
673 Self::CBAB => "CBAB",
674 Self::CEAB => "CEAB",
675 Self::CFAB => "CFAB",
676 }, f)
677 }
678}
679
680impl TryFrom<&str> for ESFSignature {
681 type Error = RLibError;
682
683 fn try_from(value: &str) -> Result<Self, Self::Error> {
684 match value {
685 "CAAB" => Ok(Self::CAAB),
686 "CBAB" => Ok(Self::CBAB),
687 "CEAB" => Ok(Self::CEAB),
688 "CFAB" => Ok(Self::CFAB),
689 _ => Err(RLibError::UnknownESFSignature(value.to_string())),
690 }
691 }
692}
693
694impl TryFrom<Vec<u8>> for ESFSignature {
695 type Error = RLibError;
696
697 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
698 match value.as_slice().try_into()? {
699 SIGNATURE_CAAB => Ok(Self::CAAB),
700 SIGNATURE_CBAB => Ok(Self::CBAB),
701 SIGNATURE_CEAB => Ok(Self::CEAB),
702 SIGNATURE_CFAB => Ok(Self::CFAB),
703 _ => Err(RLibError::UnknownESFSignatureBytes(value[0], value[1])),
704 }
705 }
706}
707
708impl From<ESFSignature> for Vec<u8> {
709 fn from(value: ESFSignature) -> Self {
710 match value {
711 ESFSignature::CAAB => SIGNATURE_CAAB.to_vec(),
712 ESFSignature::CBAB => SIGNATURE_CBAB.to_vec(),
713 ESFSignature::CEAB => SIGNATURE_CEAB.to_vec(),
714 ESFSignature::CFAB => SIGNATURE_CFAB.to_vec(),
715 }
716 }
717}
718
719impl Decodeable for ESF {
720
721 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
722 let mut esf = Self::default();
723
724 let sig_bytes = data.read_slice(4, false)?;
725 esf.signature = ESFSignature::try_from(sig_bytes.to_vec())?;
726
727 match esf.signature {
728 ESFSignature::CAAB => Self::read_caab(&mut esf, data)?,
729 ESFSignature::CBAB => Self::read_cbab(&mut esf, data)?,
730 _ => return Err(RLibError::DecodingESFUnsupportedSignature(sig_bytes[0], sig_bytes[1])),
731 };
732
733 // Debugging code.
734 //use std::io::Write;
735 //let mut x = std::fs::File::create("ceo.json")?;
736 //x.write_all(&serde_json::to_string_pretty(&esf).unwrap().as_bytes())?;
737
738 Ok(esf)
739 }
740}
741
742impl Encodeable for ESF {
743
744 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
745 let sig_bytes: Vec<u8> = Vec::from(self.signature);
746 buffer.write_all(&sig_bytes)?;
747
748 match self.signature {
749 ESFSignature::CAAB => self.save_caab(buffer, extra_data),
750 ESFSignature::CBAB => self.save_cbab(buffer, extra_data),
751 _ => Err(RLibError::EncodingESFUnsupportedSignature(self.signature.to_string())),
752 }
753 }
754}