Skip to main content

rpfm_lib/files/db/
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//! Database table files for Total War game data.
12//!
13//! DB files are the primary data storage format in Total War games, containing game data
14//! organized into tables similar to relational database tables. Each table stores structured
15//! data like units, buildings, technologies, and campaign settings.
16//!
17//! # Overview
18//!
19//! DB tables are binary files with a sequential format requiring a schema definition to decode.
20//! The definition specifies the column types and order, and tables can be versioned to handle
21//! format changes across game updates.
22//!
23//! Key characteristics:
24//! - **Schema-dependent**: Requires a definition from the schema to decode
25//! - **Versioned**: Optional version number in header for format evolution
26//! - **Fragmented**: Tables can be split across multiple files
27//! - **Binary**: Tightly packed binary format for efficient storage
28//!
29//! # Table Structure
30//!
31//! Each DB file consists of a header followed by row data. The header contains metadata
32//! like version, GUID (for some games), and row count. The data section is a sequence
33//! of rows matching the table definition.
34//!
35//! # Schema Definitions
36//!
37//! To decode a DB table, you need:
38//! 1. **Table name**: Identifies which definition to use (e.g., `"units_tables"`)
39//! 2. **Schema**: Contains definitions for all known table versions
40//! 3. **Version**: Optional version number from the header (defaults to 0)
41//!
42//! See the [`Schema`](crate::schema) module for details on definitions and the
43//! [`Table`](crate::files::table) module for the table data structure.
44//!
45//! # DB Structure
46//!
47//! ## Header
48//!
49//! | Bytes  | Type            | Data                                                         |
50//! | ------ | --------------- | ------------------------------------------------------------ |
51//! | 4      | &\[[u8]\]       | GUID Marker. Optional.                                       |
52//! | 2 + 72 | Sized StringU16 | GUID. Only present if GUID Marker is present too.            |
53//! | 4      | &\[[u8]\]       | Version Marker. Optional.                                    |
54//! | 4      | [u32]           | Version of the table. Only present if Version Marker is too. |
55//! | 1      | [bool]          | Unknown. Probably a bool because it's always either 0 or 1.  |
56//! | 4      | [u32]           | Amount of entries on the table.                              |
57//!
58//! ## Data
59//!
60//! The data structure depends on the definition of the table.
61
62use csv::{StringRecordsIter, Writer};
63use getset::Getters;
64use itertools::Itertools;
65#[cfg(feature = "integration_sqlite")]use r2d2::Pool;
66#[cfg(feature = "integration_sqlite")]use r2d2_sqlite::SqliteConnectionManager;
67use rayon::prelude::*;
68use serde_derive::{Serialize, Deserialize};
69use uuid::Uuid;
70
71use std::borrow::Cow;
72#[cfg(test)] use std::collections::BTreeMap;
73use std::collections::{HashMap, HashSet};
74use std::fs::File;
75use std::io::SeekFrom;
76
77use crate::binary::{ReadBytes, WriteBytes};
78use crate::error::{RLibError, Result};
79use crate::files::{Container, ContainerPath, DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable, FileType, table::{decode_table, DecodedData, local::TableInMemory, Table}, pack::Pack, RFileDecoded};
80#[cfg(test)] use crate::schema::FieldType;
81use crate::schema::{Definition, DefinitionPatch, Field, Schema};
82use crate::utils::check_size_mismatch;
83
84/// If this sequence is found, the DB Table has a GUID after it.
85const GUID_MARKER: &[u8] = &[253, 254, 252, 255];
86
87/// If this sequence is found, the DB Table has a version number after it.
88const VERSION_MARKER: &[u8] = &[252, 253, 254, 255];
89
90#[cfg(test)] mod db_test;
91
92//---------------------------------------------------------------------------//
93//                              Enum & Structs
94//---------------------------------------------------------------------------//
95
96/// In-memory representation of a decoded DB table.
97///
98/// Holds a complete DB table including header metadata and row data. The table data
99/// is stored as a [`TableInMemory`] which provides access to rows and columns.
100///
101/// # Fields
102///
103/// * `mysterious_byte` - Boolean flag of unknown purpose (observed as `0` or `1`)
104/// * `guid` - Globally Unique Identifier for this table instance
105/// * `table` - The actual table data with definition and rows
106///
107/// # Getters
108///
109/// Fields have public getters via the `getset` crate:
110/// - `mysterious_byte()` - Get the mysterious byte value
111/// - `guid()` - Get the table's GUID
112/// - `table()` - Get reference to the table data
113///
114/// # The Mysterious Byte
115///
116/// The purpose of this byte is unknown, but it appears in all DB tables. Observed values
117/// are `0` or `1` (interpreted as boolean). In Warhammer 2, a value of `0` can cause
118/// crashes when tables are loaded by the game.
119///
120/// # GUID Handling
121///
122/// GUIDs are only present in some games (e.g., Warhammer series). Older games like
123/// Napoleon and Empire don't use GUIDs, and adding them can crash those games.
124/// The encoding process respects the game's GUID requirements.
125///
126/// # Example
127///
128/// ```ignore
129/// use rpfm_lib::files::{Decodeable, db::DB, DecodeableExtraData, table::Table};
130/// use rpfm_lib::schema::Schema;
131/// use std::io::Cursor;
132///
133/// # let schema = Schema::default();
134/// # let table_data = vec![];
135/// let mut extra = DecodeableExtraData::default();
136/// extra.set_schema(Some(&schema));
137/// extra.set_table_name(Some("units_tables"));
138///
139/// let mut reader = Cursor::new(table_data);
140/// let db = DB::decode(&mut reader, &Some(extra)).unwrap();
141///
142/// // Access table data
143/// let row_count = db.table().len();
144/// ```
145#[derive(PartialEq, Clone, Debug, Getters, Serialize, Deserialize)]
146#[getset(get = "pub")]
147pub struct DB {
148
149    /// Boolean flag of unknown purpose (always `0` or `1`).
150    ///
151    /// In Warhammer 2, a value of `0` can crash the game when loading tables.
152    mysterious_byte: bool,
153
154    /// Globally Unique Identifier for this table instance.
155    ///
156    /// Only present in newer games. Empty string for games without GUID support.
157    guid: String,
158
159    /// The table data including definition and rows.
160    table: TableInMemory,
161}
162
163//---------------------------------------------------------------------------//
164//                           Implementation of DB
165//---------------------------------------------------------------------------//
166
167impl Decodeable for DB {
168
169    fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
170        let extra_data = extra_data.as_ref().ok_or(RLibError::DecodingMissingExtraData)?;
171        let schema = extra_data.schema.ok_or_else(|| RLibError::DecodingMissingExtraDataField("schema".to_owned()))?;
172        let table_name = extra_data.table_name.ok_or_else(|| RLibError::DecodingMissingExtraDataField("table_name".to_owned()))?;
173        let return_incomplete = extra_data.return_incomplete;
174
175        let (version, mysterious_byte, guid, entry_count) = Self::read_header(data)?;
176
177        // Try to get the table_definition for this table, if exists.
178        let definitions = schema.definitions_by_table_name(table_name).ok_or({
179            if entry_count == 0 {
180                RLibError::DecodingDBNoDefinitionsFoundAndEmptyFile
181            } else {
182                RLibError::DecodingDBNoDefinitionsFound
183            }
184        })?;
185
186        // Try to decode the table.
187        let len = data.len()?;
188        let table = if version == 0 {
189            let mut altered = false;
190            let index_reset = data.stream_position()?;
191
192            // For version 0 tables, get all definitions between 0 and -99, and get the first one that works.
193            let mut working_definition = Err(RLibError::DecodingDBNoDefinitionsFound);
194            for definition in definitions.iter().filter(|definition| *definition.version() < 1) {
195
196                // First, reset the index in case it was changed in a previous iteration.
197                // Then, check if the definition works.
198                data.seek(SeekFrom::Start(index_reset))?;
199                let db = decode_table(data, definition, Some(entry_count), return_incomplete, &mut altered);
200                if db.is_ok() && data.stream_position()? == len {
201                    working_definition = Ok(definition);
202                    break;
203                }
204            }
205
206            let definition = working_definition?;
207            let definition_patch = schema.patches_for_table(table_name).cloned().unwrap_or_default();
208
209            // Reset the index before the table, and now decode the table with proper backend support.
210            data.seek(SeekFrom::Start(index_reset))?;
211            TableInMemory::decode(data, definition, &definition_patch, Some(entry_count), return_incomplete, table_name)?
212        }
213
214        // For +0 versions, we expect unique definitions.
215        else {
216
217            let definition = definitions.iter()
218                .find(|definition| *definition.version() == version)
219                .ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
220
221            let definition_patch = schema.patches_for_table(table_name).cloned().unwrap_or_default();
222            TableInMemory::decode(data, definition, &definition_patch, Some(entry_count), return_incomplete, table_name)?
223        };
224
225        // If we are not in the last byte, it means we didn't parse the entire file, which means this file is corrupt, or the decoding failed and we bailed early.
226        //
227        // If we have return_incomplete enabled, we pass whatever we got decoded into this error.
228        check_size_mismatch(data.stream_position()? as usize, len as usize).map_err(|error| {
229            RLibError::DecodingTableIncomplete(error.to_string(), Box::new(table.clone()))
230        })?;
231
232        // If we've reached this, we've successfully decoded the table.
233        Ok(Self {
234            mysterious_byte,
235            guid,
236            table,
237        })
238    }
239}
240
241impl Encodeable for DB {
242
243    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()> {
244        let table_has_guid = if let Some (ref extra_data) = extra_data { extra_data.table_has_guid } else { false };
245        let regenerate_table_guid = if let Some (ref extra_data) = extra_data { extra_data.regenerate_table_guid } else { false };
246
247        // Napoleon and Empire do not have GUID, and adding it to their tables crash both games.
248        // So for those two games, remember that you have to ignore the GUID_MARKER and the GUID itself.
249        if table_has_guid {
250            buffer.write_all(GUID_MARKER)?;
251            if regenerate_table_guid || self.guid.is_empty() {
252                buffer.write_sized_string_u16(&Uuid::new_v4().to_string())?;
253            } else {
254                buffer.write_sized_string_u16(&self.guid)?;
255            }
256        }
257
258        // Only put version numbers on tables with an actual version.
259        if *self.table.definition().version() > 0 {
260            buffer.write_all(VERSION_MARKER)?;
261            buffer.write_i32(*self.table.definition().version())?;
262        }
263
264        buffer.write_bool(self.mysterious_byte)?;
265        buffer.write_u32(self.table.len() as u32)?;
266
267        self.table.encode(buffer)
268    }
269}
270
271impl DB {
272
273    /// Creates a new empty DB table with the specified definition.
274    ///
275    /// Initializes a DB table with no rows but with the structure defined by the provided
276    /// definition. The mysterious byte is set to `true` (safe default) and the GUID is empty.
277    ///
278    /// # Arguments
279    ///
280    /// * `definition` - Schema definition specifying column types and structure
281    /// * `definition_patch` - Optional patches to modify the definition
282    /// * `table_name` - Name of the table (for internal tracking)
283    ///
284    /// # Returns
285    ///
286    /// A new empty DB table ready to have rows added.
287    ///
288    /// # Example
289    ///
290    /// ```ignore
291    /// use rpfm_lib::files::{db::DB, table::Table};
292    /// use rpfm_lib::schema::Definition;
293    ///
294    /// # let definition = Definition::default();
295    /// let db = DB::new(&definition, None, "units_tables");
296    /// assert_eq!(db.table().len(), 0);
297    /// ```
298    pub fn new(definition: &Definition, definition_patch: Option<&DefinitionPatch>, table_name: &str) -> Self {
299        let table = TableInMemory::new(definition, definition_patch, table_name);
300
301        Self {
302            mysterious_byte: true,
303            guid: String::new(),
304            table,
305        }
306    }
307
308    /// Decodes the header section of a DB table.
309    ///
310    /// Reads the header bytes to extract metadata without decoding the full table data.
311    /// This is useful for inspecting table properties before committing to a full decode.
312    ///
313    /// # Header Format
314    ///
315    /// The header contains optional and required fields:
316    /// 1. **Optional GUID Marker** (`0xFD 0xFE 0xFC 0xFF`) + 2-byte sized UTF-16 string
317    /// 2. **Optional Version Marker** (`0xFC 0xFD 0xFE 0xFF`) + 4-byte signed integer
318    /// 3. **Mysterious Byte** (1 byte boolean)
319    /// 4. **Entry Count** (4-byte unsigned integer)
320    ///
321    /// # Arguments
322    ///
323    /// * `data` - Reader positioned at the start of the DB table
324    ///
325    /// # Returns
326    ///
327    /// A tuple containing:
328    /// - `version` - Table version number (0 if no version marker present)
329    /// - `mysterious_byte` - Unknown boolean flag
330    /// - `guid` - Table GUID (empty string if no GUID marker present)
331    /// - `entry_count` - Number of rows in the table
332    ///
333    /// # Errors
334    ///
335    /// Returns [`RLibError::DecodingDBNotADBTable`] if:
336    /// - The data is less than 5 bytes (minimum valid header size)
337    /// - The data doesn't conform to the DB header format
338    ///
339    /// # Side Effects
340    ///
341    /// After reading, the reader is positioned at the start of the table data section.
342    pub fn read_header<R: ReadBytes>(data: &mut R) -> Result<(i32, bool, String, u32)> {
343
344        // 5 is the minimum amount of bytes a valid DB Table can have. If there is less, either the table is broken,
345        // or the data is not from a DB Table.
346        if data.len()? < 5 {
347            return Err(RLibError::DecodingDBNotADBTable);
348        }
349
350        // If there is a GUID_MARKER, get the GUID and store it. If not, just store an empty string.
351        let guid = if data.read_slice(4, false)? == GUID_MARKER {
352            data.read_sized_string_u16()?
353        } else {
354            data.seek(SeekFrom::Current(-4))?;
355            String::new()
356        };
357
358        // If there is a VERSION_MARKER, we get the version (4 bytes for the marker, 4 for the version).
359        // Otherwise, we default to version 0.
360        let version = if data.read_slice(4, false)? == VERSION_MARKER {
361            data.read_i32()?
362        } else {
363            data.seek(SeekFrom::Current(-4))?;
364            0
365        };
366
367        // We get the rest of the data from the header.
368        let mysterious_byte = data.read_bool()?;
369        let entry_count = data.read_u32()?;
370        Ok((version, mysterious_byte, guid, entry_count))
371    }
372
373    /// Returns the schema definition for this DB table.
374    pub fn definition(&self) -> &Definition {
375        self.table.definition()
376    }
377
378    /// Returns the definition patches applied to this DB table.
379    pub fn patches(&self) -> &DefinitionPatch {
380        self.table.patches()
381    }
382
383    /// Returns the table name (e.g., `"units_tables"`).
384    pub fn table_name(&self) -> &str {
385        self.table.table_name()
386    }
387
388    /// Returns the table name without the `"_tables"` suffix.
389    ///
390    /// # Panics
391    ///
392    /// Panics if the table name doesn't end with `"_tables"`.
393    pub fn table_name_without_tables(&self) -> String {
394
395        // Note: it needs this check because this explodes if instead of "_tables" we have non-ascii characters.
396        if self.table_name().ends_with("_tables") {
397            self.table_name().to_owned().drain(..self.table_name().len() - 7).collect()
398        } else {
399            panic!("Either the code is broken, or someone with a few loose screws has renamed the fucking table folder. Crash for now, may return an error in the future.")
400        }
401    }
402
403    /// Returns the table rows as a slice of decoded data.
404    pub fn data(&'_ self) -> Cow<'_, [Vec<DecodedData>]> {
405        self.table.data()
406    }
407
408    /// Loads table data from a SQLite database, replacing current contents.
409    #[cfg(feature = "integration_sqlite")]
410    pub fn sql_to_db(&mut self, pool: &Pool<SqliteConnectionManager>, pack_name: &str, file_name: &str) -> Result<()> {
411        self.table.sql_to_db(pool, pack_name, file_name)
412    }
413
414    /// Returns a mutable reference to the table rows.
415    ///
416    /// Ensure modifications maintain valid structure matching the definition.
417    pub fn data_mut(&mut self) -> &mut Vec<Vec<DecodedData>> {
418        self.table.data_mut()
419    }
420
421    /// Replaces all table data with the provided rows.
422    ///
423    /// # Errors
424    ///
425    /// Returns an error if rows don't match the table definition structure.
426    pub fn set_data(&mut self, data: &[Vec<DecodedData>]) -> Result<()> {
427        self.table.set_data(data)
428    }
429
430    /// Creates a new row with default values from the table definition.
431    pub fn new_row(&self) -> Vec<DecodedData> {
432        self.table().new_row()
433    }
434
435    /// Returns a test definition with all field types for unit testing.
436    #[cfg(test)]
437    pub fn test_definition() -> Definition {
438        let mut definition = Definition::new(-100, None);
439        let mut fields = vec![];
440
441        fields.push(Field::new("bool".to_owned(), FieldType::Boolean, false, Some("true".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
442        fields.push(Field::new("f32".to_owned(), FieldType::F32, false, Some("1.0".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
443        fields.push(Field::new("f64".to_owned(), FieldType::F64, false, Some("2.0".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
444        fields.push(Field::new("i16".to_owned(), FieldType::I16, false, Some("3".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
445        fields.push(Field::new("i32".to_owned(), FieldType::I32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
446        fields.push(Field::new("i64".to_owned(), FieldType::I64, false, Some("5".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
447        fields.push(Field::new("colour".to_owned(), FieldType::ColourRGB, false, Some("ABCDEF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
448        fields.push(Field::new("stringu8".to_owned(), FieldType::StringU8, false, Some("AAAA".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
449        fields.push(Field::new("stringu16".to_owned(), FieldType::StringU16, false, Some("BBBB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
450        fields.push(Field::new("optionali16".to_owned(), FieldType::OptionalI16, false, Some("3".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
451        fields.push(Field::new("optionali32".to_owned(), FieldType::OptionalI32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
452        fields.push(Field::new("optionali64".to_owned(), FieldType::OptionalI64, false, Some("5".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
453        fields.push(Field::new("optionalstringu8".to_owned(), FieldType::OptionalStringU8, false, Some("Opt".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
454        fields.push(Field::new("optionalstringu16".to_owned(), FieldType::OptionalStringU16, false, Some("Opt".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
455        fields.push(Field::new("sequenceu16".to_owned(), FieldType::SequenceU16(Box::new(Definition::new(-100, None))), false, None, false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
456        fields.push(Field::new("sequenceu32".to_owned(), FieldType::SequenceU32(Box::new(Definition::new(-100, None))), false, None, false, None, None, None, String::new(), 0, 0, BTreeMap::new(), None));
457
458        // Special fields that use postprocessing.
459        fields.push(Field::new("merged_colours_1_r".to_owned(), FieldType::I32, false, Some("AB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
460        fields.push(Field::new("merged_colours_1_g".to_owned(), FieldType::I32, false, Some("CD".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
461        fields.push(Field::new("merged_colours_1_b".to_owned(), FieldType::I32, false, Some("EF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(0)));
462        fields.push(Field::new("bitwise_values".to_owned(), FieldType::I32, false, Some("4".to_string()), false, None, None, None, String::new(), 0, 5, BTreeMap::new(), None));
463        fields.push(Field::new("enum_values".to_owned(), FieldType::I32, false, Some("8".to_string()), false, None, None, None, String::new(), 0, 0, {
464            let mut bt = BTreeMap::new();
465            bt.insert(0, "test0".to_owned());
466            bt.insert(1, "test1".to_owned());
467            bt.insert(2, "test2".to_owned());
468            bt.insert(3, "test3".to_owned());
469            bt
470        }, None));
471
472        fields.push(Field::new("merged_colours_2_r".to_owned(), FieldType::I32, false, Some("AB".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
473        fields.push(Field::new("merged_colours_2_g".to_owned(), FieldType::I32, false, Some("CD".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
474        fields.push(Field::new("merged_colours_2_b".to_owned(), FieldType::I32, false, Some("EF".to_string()), false, None, None, None, String::new(), 0, 0, BTreeMap::new(), Some(1)));
475
476        // TODO: add combined colour columns for testing.
477
478        definition.set_fields(fields);
479        definition
480    }
481
482    /// Returns the column index for a given column name, or `None` if not found.
483    pub fn column_position_by_name(&self, column_name: &str) -> Option<usize> {
484        self.table.column_position_by_name(column_name)
485    }
486
487    /// Returns the number of rows in the table.
488    pub fn len(&self) -> usize {
489        self.table.len()
490    }
491
492    /// Returns `true` if the table has no rows.
493    pub fn is_empty(&self) -> bool {
494        self.table.is_empty()
495    }
496
497    /// Replaces the table definition and migrates existing data to match.
498    ///
499    /// Use this to update tables to a newer schema version. Data is converted
500    /// between compatible types where possible.
501    pub fn set_definition(&mut self, new_definition: &Definition) {
502        self.table.set_definition(new_definition);
503    }
504
505    /// Alias for [`set_definition`](Self::set_definition).
506    pub fn update(&mut self, new_definition: &Definition) {
507        self.set_definition(new_definition)
508    }
509
510    /// Performs a cascade update of a value across all referencing tables in a Pack.
511    ///
512    /// When a key field value is changed, this function finds all tables that reference
513    /// that field and updates them accordingly. It also updates corresponding Loc entries
514    /// if the edited field affects localisation keys.
515    ///
516    /// # Arguments
517    ///
518    /// * `pack` - The Pack to search and update.
519    /// * `schema` - Schema containing table definitions and reference information.
520    /// * `table_name` - Name of the source table (e.g., `"units_tables"`).
521    /// * `field` - The field being edited.
522    /// * `definition` - Definition of the source table.
523    /// * `value_before` - Original value being replaced.
524    /// * `value_after` - New value to set.
525    ///
526    /// # Returns
527    ///
528    /// List of paths where references were found and updated.
529    pub fn cascade_edition(pack: &mut Pack, schema: &Schema, table_name: &str, field: &Field, definition: &Definition, value_before: &str, value_after: &str) -> Vec<ContainerPath> {
530
531        // So, how does this work:
532        // - First, we need to calculate all related tables/columns. This includes the parent columns if this is a reference, and all references to this field.
533        // - Second, we need to calculate all related loc fields corresponding to the edited fiels.
534        // - Third, we edit the table entries.
535        // - Fourth, we edit the loc entries.
536        let mut edited_paths = vec![];
537
538        // If we're not changing anything, don't bother performing an edition.
539        if value_before == value_after {
540            return vec![];
541        }
542
543        // Just in case we're in a reference field, find the source, and trigger the edition from there.
544        let mut definition = definition.clone();
545        let patches = Some(definition.patches().clone());
546        let mut field = field.clone();
547        let mut table_name = table_name.to_owned();
548        while let Some((ref_table, ref_column)) = field.is_reference(patches.as_ref()) {
549            let ref_table_name = format!("{ref_table}_tables");
550            let table_folder = format!("db/{ref_table_name}");
551            let parent_files = pack.files_by_type_and_paths_mut(&[FileType::DB], &[ContainerPath::Folder(table_folder.to_owned())], true);
552            if !parent_files.is_empty() {
553                if let Ok(RFileDecoded::DB(table)) = parent_files[0].decoded() {
554                    if let Some(index) = table.definition().column_position_by_name(&ref_column) {
555                        definition = table.definition().clone();
556                        field = definition.fields_processed()[index].clone();
557                        table_name = table.table_name().to_owned();
558                        continue;
559                    }
560                }
561            }
562
563            break;
564        }
565
566        // Get the tables/rows that need to be edited.
567        let fields_processed = definition.fields_processed();
568        let fields_localised = definition.localised_fields();
569        let (mut ref_table_data, _) = schema.tables_and_columns_referencing_our_own(&table_name, field.name(), &fields_processed, fields_localised);
570
571        // Add the source table and column to the list to edit.
572        ref_table_data.insert(table_name, vec![field.name().to_owned()]);
573
574        let container_paths = ref_table_data.keys().map(|ref_table_name| ContainerPath::Folder("db/".to_owned() + ref_table_name)).collect::<Vec<_>>();
575        let mut files = pack.files_by_paths_mut(&container_paths, true);
576        let mut loc_keys: Vec<(String, String)> = vec![];
577
578        for file in files.iter_mut() {
579            let path = file.path_in_container();
580            if let Ok(RFileDecoded::DB(table)) = file.decoded_mut() {
581                let fields_processed = table.definition().fields_processed();
582                let fields_localised = table.definition().localised_fields().to_vec();
583                let localised_order = table.definition().localised_key_order().to_vec();
584                let patches = table.definition().patches().clone();
585                let table_name = table.table_name().to_owned();
586                let table_name_no_tables = table.table_name_without_tables();
587                let table_data = table.data_mut();
588
589                let mut keys_edited = vec![];
590
591                // Find the column to edit within the table.
592                let column_indexes = fields_processed.iter()
593                    .enumerate()
594                    .filter_map(|(index, field)| if ref_table_data[&table_name].iter().any(|name| name == field.name()) { Some(index) } else { None })
595                    .collect::<Vec<usize>>();
596
597                // Then, go through all the rows and perform the edits.
598                for row in table_data.iter_mut() {
599                    for column in &column_indexes {
600
601                        // TODO: FIX THIS SHIT. It duplicates ALL DATA IN ALL TABLES CHECK.
602                        let row_copy = row.to_vec();
603
604                        if let Some(field_data) = row.get_mut(*column) {
605                            match field_data {
606                                DecodedData::StringU8(field_data) |
607                                DecodedData::StringU16(field_data) |
608                                DecodedData::OptionalStringU8(field_data) |
609                                DecodedData::OptionalStringU16(field_data) => {
610
611                                    // Only edit exact matches.
612                                    if field_data == value_before {
613                                        let mut locs_edited = vec![];
614
615                                        // If it's a key, calculate the relevant before and after loc keys.
616                                        let is_key = fields_processed[*column].is_key(Some(&patches));
617                                        if is_key {
618                                            for loc_field in &fields_localised {
619                                                let loc_key = localised_order.iter().map(|pos| row_copy[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
620                                                locs_edited.push(format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key));
621                                            }
622                                        }
623
624                                        *field_data = value_after.to_owned();
625
626                                        if !locs_edited.is_empty() {
627                                            for (index, loc_field) in fields_localised.iter().enumerate() {
628                                                if let Some(key_old) = locs_edited.get(index) {
629                                                    let loc_key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
630                                                    let key_new = format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key);
631                                                    keys_edited.push((key_old.to_owned(), key_new.to_owned()))
632                                                }
633                                            }
634                                        }
635
636                                        if !edited_paths.contains(&path) {
637                                            edited_paths.push(path.clone());
638                                        }
639                                    }
640                                }
641                                _ => continue
642                            }
643                        }
644                    }
645                }
646
647                // If we edited a key field in the table, check if we need to edit any relevant loc field.
648                if !keys_edited.is_empty() {
649                    loc_keys.append(&mut keys_edited);
650                }
651            }
652        }
653
654        // Now, we find and replace all the loc keys we have to change.
655        let mut loc_files = pack.files_by_type_mut(&[FileType::Loc]);
656        for file in &mut loc_files {
657            let path = file.path_in_container();
658            if let Ok(RFileDecoded::Loc(data)) = file.decoded_mut() {
659                let data = data.data_mut();
660                for row in data.iter_mut() {
661                    if let Some(
662                        DecodedData::StringU8(field_data) |
663                        DecodedData::StringU16(field_data) |
664                        DecodedData::OptionalStringU8(field_data) |
665                        DecodedData::OptionalStringU16(field_data)
666                    ) = row.get_mut(0) {
667                        for (key_old, key_new) in &loc_keys {
668                            if field_data == key_old {
669                                *field_data = key_new.to_owned();
670
671                                if !edited_paths.contains(&path) {
672                                    edited_paths.push(path.clone());
673                                }
674                            }
675                        }
676                    }
677                }
678            }
679        }
680
681        edited_paths
682    }
683
684    /// Merges multiple DB tables into a single new table.
685    ///
686    /// Combines all rows from the source tables. The first table's definition and
687    /// patches are used for the merged result. All source tables are converted to
688    /// match this definition before merging.
689    ///
690    /// # Errors
691    ///
692    /// Returns an error if:
693    /// - Tables have different names (can't merge `units_tables` with `buildings_tables`)
694    /// - Fewer than 2 tables are provided
695    pub fn merge(sources: &[&Self]) -> Result<Self> {
696
697        let table_names = sources.iter().map(|file| file.table_name()).collect::<HashSet<_>>();
698        if table_names.len() > 1 {
699            return Err(RLibError::RFileMergeTablesDifferentNames);
700        }
701
702        if sources.len() < 2 {
703            return Err(RLibError::RFileMergeTablesNotEnoughTablesProvided);
704        }
705
706        let mut new_table = Self::new(sources[0].definition(), Some(sources[0].patches()), sources[0].table_name());
707        let sources = sources.par_iter()
708            .map(|table| {
709                let mut table = table.table().clone();
710                table.set_definition(new_table.definition());
711                table
712            })
713            .collect::<Vec<_>>();
714
715        let new_data = sources.par_iter()
716            .map(|table| table.data().to_vec())
717            .flatten()
718            .collect::<Vec<_>>();
719        new_table.set_data(&new_data)?;
720
721        Ok(new_table)
722    }
723
724    /// Imports a DB table from TSV (tab-separated values) format.
725    ///
726    /// # Arguments
727    ///
728    /// * `records` - CSV reader iterator over TSV records.
729    /// * `field_order` - Mapping of column positions to field names.
730    /// * `schema` - Schema containing the table definition.
731    /// * `table_name` - Name of the table (e.g., `"units_tables"`).
732    /// * `table_version` - Version of the table definition to use.
733    ///
734    /// # Errors
735    ///
736    /// Returns an error if no matching definition is found in the schema.
737    pub fn tsv_import(records: StringRecordsIter<File>, field_order: &HashMap<u32, String>, schema: &Schema, table_name: &str, table_version: i32) -> Result<Self> {
738        let definition = schema.definition_by_name_and_version(table_name, table_version).ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
739        let definition_patch = schema.patches_for_table(table_name);
740        let table = TableInMemory::tsv_import(records, definition, field_order, table_name, definition_patch)?;
741        let db = DB::from(table);
742        Ok(db)
743    }
744
745    /// Exports the DB table to TSV (tab-separated values) format.
746    ///
747    /// # Arguments
748    ///
749    /// * `writer` - CSV writer for the output file.
750    /// * `table_path` - Path used in the TSV metadata header.
751    /// * `keys_first` - If `true`, key columns are written before non-key columns.
752    pub fn tsv_export(&self, writer: &mut Writer<File>, table_path: &str, keys_first: bool) -> Result<()> {
753        self.table.tsv_export(writer, table_path, keys_first)
754    }
755
756    /// Returns `true` if data was modified during decoding (e.g., invalid values corrected).
757    pub fn altered(&self) -> bool {
758        *self.table.altered()
759    }
760
761    /// Generates combined primary keys to populate the `twad_key_deletes` table.
762    ///
763    /// Different tables use different key concatenation rules. This function handles
764    /// the table-specific key format for each known table type.
765    pub fn generate_twad_key_deletes_keys(&self, keys: &mut HashSet<String>) {
766        let definition = self.definition();
767        match self.table_name() {
768
769            // Order reversed vs dave schemas: agent + attribute.
770            "agent_to_agent_attributes_tables" => {
771                let key_pos_0 = definition.column_position_by_name("agent").unwrap_or_default();
772                let key_pos_1 = definition.column_position_by_name("attribute").unwrap_or_default();
773
774                keys.extend(self.data()
775                    .iter()
776                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
777                    .collect::<Vec<_>>()
778                );
779            }
780
781            // Uses ; for concatenating keys: attacker + ";" + defender.
782            "animation_set_prebattle_group_view_configurations_tables" => {
783                let key_pos_0 = definition.column_position_by_name("attacker").unwrap_or_default();
784                let key_pos_1 = definition.column_position_by_name("defender").unwrap_or_default();
785
786                keys.extend(self.data()
787                    .iter()
788                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
789                    .collect::<Vec<_>>()
790                );
791            }
792
793            // Uses " | ", with whitespace, for concatenating keys: armory_item + " | "  + variant.
794            "armory_item_variants_tables" => {
795                let key_pos_0 = definition.column_position_by_name("armory_item").unwrap_or_default();
796                let key_pos_1 = definition.column_position_by_name("variant").unwrap_or_default();
797
798                keys.extend(self.data()
799                    .iter()
800                    .map(|x| x[key_pos_0].data_to_string().to_string() + " | " + &x[key_pos_1].data_to_string())
801                    .collect::<Vec<_>>()
802                );
803            }
804
805            // It includes the item_type string two times: item_type + currency_type + item_type.
806            "battle_currency_deployables_cost_values_tables" => {
807                let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
808                let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
809
810                keys.extend(self.data()
811                    .iter()
812                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
813                    .collect::<Vec<_>>()
814                );
815            }
816
817            // It includes the item_type string two times: item_type + currency_type + item_type.
818            "battle_currency_units_cost_values_tables" => {
819                let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
820                let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
821
822                keys.extend(self.data()
823                    .iter()
824                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
825                    .collect::<Vec<_>>()
826                );
827            }
828
829            // It includes the army_name string two times: army_name + siege_item + army_name.
830            "battle_set_pieces_siege_items_tables" => {
831                let key_pos_0 = definition.column_position_by_name("army_name").unwrap_or_default();
832                let key_pos_1 = definition.column_position_by_name("siege_item").unwrap_or_default();
833
834                keys.extend(self.data()
835                    .iter()
836                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
837                    .collect::<Vec<_>>()
838                );
839            }
840
841            // Uses ; for concatenating keys: distribution_profile_key + ";" + agent_type_key.
842            "cai_agent_type_distribution_profile_junctions_tables" => {
843                let key_pos_0 = definition.column_position_by_name("distribution_profile_key").unwrap_or_default();
844                let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
845
846                keys.extend(self.data()
847                    .iter()
848                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
849                    .collect::<Vec<_>>()
850                );
851            }
852
853            // Uses ; for concatenating keys: recruitment_profile_key + ";" + agent_type_key.
854            "cai_agent_type_recruitment_profile_junctions_tables" => {
855                let key_pos_0 = definition.column_position_by_name("recruitment_profile_key").unwrap_or_default();
856                let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
857
858                keys.extend(self.data()
859                    .iter()
860                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
861                    .collect::<Vec<_>>()
862                );
863            }
864
865            // Uses ; for concatenating keys: budget_allocation_key + ";" + budget_context_key + "; + budget_policy_key.
866            "cai_personalities_budget_allocation_policy_junctions_tables" => {
867                let key_pos_0 = definition.column_position_by_name("budget_allocation_key").unwrap_or_default();
868                let key_pos_1 = definition.column_position_by_name("budget_context_key").unwrap_or_default();
869                let key_pos_2 = definition.column_position_by_name("budget_policy_key").unwrap_or_default();
870
871                keys.extend(self.data()
872                    .iter()
873                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string() + ";" + &x[key_pos_2].data_to_string())
874                    .collect::<Vec<_>>()
875                );
876            }
877
878            // Uses ; for concatenating keys: building_key + ";" + policy_key.
879            "cai_personalities_construction_preference_policy_building_junctions_tables" => {
880                let key_pos_0 = definition.column_position_by_name("building_key").unwrap_or_default();
881                let key_pos_1 = definition.column_position_by_name("policy_key").unwrap_or_default();
882
883                keys.extend(self.data()
884                    .iter()
885                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
886                    .collect::<Vec<_>>()
887                );
888            }
889
890            // It includes the variable_group_key string two times: variable_group_key + variable_key + variable_group_key.
891            "cai_task_management_system_task_generator_variable_group_junctions_tables" => {
892                let key_pos_0 = definition.column_position_by_name("variable_group_key").unwrap_or_default();
893                let key_pos_1 = definition.column_position_by_name("variable_key").unwrap_or_default();
894
895                keys.extend(self.data()
896                    .iter()
897                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
898                    .collect::<Vec<_>>()
899                );
900            }
901
902            // Uses ; for concatenating keys: manager + ";" + behaviour.
903            "campaign_ai_manager_behaviour_junctions_tables" => {
904                let key_pos_0 = definition.column_position_by_name("manager").unwrap_or_default();
905                let key_pos_1 = definition.column_position_by_name("behaviour").unwrap_or_default();
906
907                keys.extend(self.data()
908                    .iter()
909                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
910                    .collect::<Vec<_>>()
911                );
912            }
913
914            // Order reversed vs dave schemas: bmd_export_types + campaign_bmd_layer_group.
915            "campaign_bmd_layer_group_bmd_export_types_junctions_tables" => {
916                let key_pos_0 = definition.column_position_by_name("bmd_export_types").unwrap_or_default();
917                let key_pos_1 = definition.column_position_by_name("campaign_bmd_layer_group").unwrap_or_default();
918
919                keys.extend(self.data()
920                    .iter()
921                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
922                    .collect::<Vec<_>>()
923                );
924            }
925
926            // Uses custom formatting, and 1 and 0 for bools: campaign_difficulty_handicap + "-" + human ? 1 : 0 + "-" + effect + optional_campaign_key.
927            "campaign_difficulty_handicap_effects_tables" => {
928                let key_pos_0 = definition.column_position_by_name("campaign_difficulty_handicap").unwrap_or_default();
929                let key_pos_1 = definition.column_position_by_name("human").unwrap_or_default();
930                let key_pos_2 = definition.column_position_by_name("effect").unwrap_or_default();
931                let key_pos_3 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
932
933                keys.extend(self.data()
934                    .iter()
935                    .map(|x| {
936                        let human = if x[key_pos_1].data_to_string() == "true" { "1" } else { "0" };
937                        x[key_pos_0].data_to_string().to_string() + "-" + human + "-" + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string()
938                    })
939                    .collect::<Vec<_>>()
940                );
941            }
942
943            // Order reversed vs dave schemas: agent_key + campaign_effect_scope_key.
944            "campaign_effect_scope_agent_junctions_tables" => {
945                let key_pos_0 = definition.column_position_by_name("agent_key").unwrap_or_default();
946                let key_pos_1 = definition.column_position_by_name("campaign_effect_scope_key").unwrap_or_default();
947
948                keys.extend(self.data()
949                    .iter()
950                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
951                    .collect::<Vec<_>>()
952                );
953            }
954
955            // Uses ; for concatenating keys: feature + ";" + group.
956            "campaign_features_tables" => {
957                let key_pos_0 = definition.column_position_by_name("feature").unwrap_or_default();
958                let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
959
960                keys.extend(self.data()
961                    .iter()
962                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
963                    .collect::<Vec<_>>()
964                );
965            }
966
967            // Uses ; for concatenating keys: marker_type + ";" + group.
968            "campaign_markers_tables" => {
969                let key_pos_0 = definition.column_position_by_name("marker_type").unwrap_or_default();
970                let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
971
972                keys.extend(self.data()
973                    .iter()
974                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
975                    .collect::<Vec<_>>()
976                );
977            }
978
979            // Uses custom formatting: faction_override.empty() ? unit : unit + "_" + faction_override.
980            "campaign_mercenary_unit_character_level_restrictions_tables" => {
981                let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
982                let key_pos_1 = definition.column_position_by_name("faction_override").unwrap_or_default();
983
984                keys.extend(self.data()
985                    .iter()
986                    .map(|x| {
987                        let fover = if x[key_pos_1].data_to_string().is_empty() {
988                            "".to_owned()
989                        } else {
990                            "_".to_string() + &x[key_pos_1].data_to_string()
991                        };
992                        x[key_pos_0].data_to_string().to_string() + &fover
993                    })
994                    .collect::<Vec<_>>()
995                );
996            }
997
998            // It includes the id string two times: id + id.
999            "campaign_movement_spline_materials_tables" => {
1000                let key_pos_0 = definition.column_position_by_name("id").unwrap_or_default();
1001
1002                keys.extend(self.data()
1003                    .iter()
1004                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_0].data_to_string())
1005                    .collect::<Vec<_>>()
1006                );
1007            }
1008
1009            // Campaign doesn't exist in newer definitions: campaign + faction.
1010            "campaign_rogue_army_leaders_tables" => {
1011                let key_pos_0 = definition.column_position_by_name("campaign");
1012                let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
1013
1014                keys.extend(self.data()
1015                    .iter()
1016                    .map(|x| {
1017                        let camp = if let Some(y) = key_pos_0 { x[y].data_to_string().to_string() } else { "".to_owned() };
1018                        camp + &x[key_pos_1].data_to_string()
1019                    })
1020                    .collect::<Vec<_>>()
1021                );
1022            }
1023
1024            // Uses ; for concatenating keys: faction + ";" + difficulty_level.
1025            "campaign_rogue_army_setups_tables" => {
1026                let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
1027                let key_pos_1 = definition.column_position_by_name("difficulty_level").unwrap_or_default();
1028
1029                keys.extend(self.data()
1030                    .iter()
1031                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1032                    .collect::<Vec<_>>()
1033                );
1034            }
1035
1036            // Order custom vs dave schemas: variable_key + campaign_name + difficulty + campaign_type.
1037            "campaigns_campaign_variables_junctions_tables" => {
1038                let key_pos_0 = definition.column_position_by_name("variable_key").unwrap_or_default();
1039                let key_pos_1 = definition.column_position_by_name("campaign_name").unwrap_or_default();
1040                let key_pos_2 = definition.column_position_by_name("difficulty").unwrap_or_default();
1041                let key_pos_3 = definition.column_position_by_name("campaign_type").unwrap_or_default();
1042
1043                keys.extend(self.data()
1044                    .iter()
1045                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string())
1046                    .collect::<Vec<_>>()
1047                );
1048            }
1049
1050            // It includes the tree_type string two times: tree_type + variable_key + tree_type.
1051            "campaign_tree_type_cultures_tables" => {
1052                let key_pos_0 = definition.column_position_by_name("tree_type").unwrap_or_default();
1053                let key_pos_1 = definition.column_position_by_name("culture").unwrap_or_default();
1054
1055                keys.extend(self.data()
1056                    .iter()
1057                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_0].data_to_string())
1058                    .collect::<Vec<_>>()
1059                );
1060            }
1061
1062            // Order reversed vs dave schemas: issuer_key + mission_key.
1063            "cdir_events_mission_issuer_junctions_tables" => {
1064                let key_pos_0 = definition.column_position_by_name("issuer_key").unwrap_or_default();
1065                let key_pos_1 = definition.column_position_by_name("mission_key").unwrap_or_default();
1066
1067                keys.extend(self.data()
1068                    .iter()
1069                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1070                    .collect::<Vec<_>>()
1071                );
1072            }
1073
1074            // Uses custom formatting: (for_army ? (for_navy ? "navy" : "army") : agent_key) + skill_rank + optional_campaign_key.
1075            "character_experience_skill_tiers_tables" => {
1076                let key_pos_0 = definition.column_position_by_name("for_army").unwrap_or_default();
1077                let key_pos_1 = definition.column_position_by_name("for_navy").unwrap_or_default();
1078                let key_pos_2 = definition.column_position_by_name("agent_key").unwrap_or_default();
1079                let key_pos_3 = definition.column_position_by_name("skill_rank").unwrap_or_default();
1080                let key_pos_4 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1081
1082                keys.extend(self.data()
1083                    .iter()
1084                    .map(|x| {
1085                        let mut ckey = String::new();
1086                        if x[key_pos_0].data_to_string() == "true" {
1087                            if x[key_pos_1].data_to_string() == "true" {
1088                                ckey.push_str("navy");
1089                            } else {
1090                                ckey.push_str("army");
1091                            }
1092                        } else {
1093                            ckey.push_str(&x[key_pos_2].data_to_string());
1094                        }
1095
1096                        ckey + &x[key_pos_3].data_to_string() + &x[key_pos_4].data_to_string()
1097                    })
1098                    .collect::<Vec<_>>()
1099                );
1100            }
1101
1102            // Order reversed vs dave schemas: battle_animations_table + culture_pack.
1103            "culture_to_battle_animation_tables_tables" => {
1104                let key_pos_0 = definition.column_position_by_name("battle_animations_table").unwrap_or_default();
1105                let key_pos_1 = definition.column_position_by_name("culture_pack").unwrap_or_default();
1106
1107                keys.extend(self.data()
1108                    .iter()
1109                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1110                    .collect::<Vec<_>>()
1111                );
1112            }
1113
1114            // Order reversed vs dave schemas: effect + action_results_additional_outcome_record + bonus_value_id.
1115            "effect_bonus_value_id_action_results_additional_outcomes_junctions_tables" => {
1116                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1117                let key_pos_1 = definition.column_position_by_name("action_results_additional_outcome_record").unwrap_or_default();
1118                let key_pos_2 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1119
1120                keys.extend(self.data()
1121                    .iter()
1122                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
1123                    .collect::<Vec<_>>()
1124                );
1125            }
1126
1127            // Order reversed vs dave schemas: effect + bonus_value + projectile.
1128            "effect_bonus_value_projectile_junctions_tables" => {
1129                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1130                let key_pos_1 = definition.column_position_by_name("bonus_value").unwrap_or_default();
1131                let key_pos_2 = definition.column_position_by_name("projectile").unwrap_or_default();
1132
1133                keys.extend(self.data()
1134                    .iter()
1135                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
1136                    .collect::<Vec<_>>()
1137                );
1138            }
1139
1140            // Uses ; for concatenating keys: effect + ";" + bonus_value_id + ";" + unit_attribute.
1141            "effect_bonus_value_unit_attribute_junctions_tables" => {
1142                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1143                let key_pos_1 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1144                let key_pos_2 = definition.column_position_by_name("unit_attribute").unwrap_or_default();
1145
1146                keys.extend(self.data()
1147                    .iter()
1148                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string() + ";" + &x[key_pos_2].data_to_string())
1149                    .collect::<Vec<_>>()
1150                );
1151            }
1152
1153            // Order reversed vs dave schemas: bonus_value_id + effect + unit_record_key.
1154            "effect_bonus_value_unit_record_junctions_tables" => {
1155                let key_pos_0 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1156                let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
1157                let key_pos_2 = definition.column_position_by_name("unit_record_key").unwrap_or_default();
1158
1159                keys.extend(self.data()
1160                    .iter()
1161                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string())
1162                    .collect::<Vec<_>>()
1163                );
1164            }
1165
1166            // It includes the affected_stat string two times: ground_type + affected_stat + affected_group + affected_stat.
1167            "ground_type_to_stat_effects_tables" => {
1168                let key_pos_0 = definition.column_position_by_name("ground_type").unwrap_or_default();
1169                let key_pos_1 = definition.column_position_by_name("affected_stat").unwrap_or_default();
1170                let key_pos_2 = definition.column_position_by_name("affected_group").unwrap_or_default();
1171
1172                keys.extend(self.data()
1173                    .iter()
1174                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_1].data_to_string())
1175                    .collect::<Vec<_>>()
1176                );
1177            }
1178
1179            // Order reversed vs dave schemas: ability + land_unit.
1180            "land_units_to_unit_abilites_junctions_tables" => {
1181                let key_pos_0 = definition.column_position_by_name("ability").unwrap_or_default();
1182                let key_pos_1 = definition.column_position_by_name("land_unit").unwrap_or_default();
1183
1184                keys.extend(self.data()
1185                    .iter()
1186                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1187                    .collect::<Vec<_>>()
1188                );
1189            }
1190
1191            // It includes the affected_stat string three times: loading_quote + campaign + campaign + campaign.
1192            "loading_screen_quotes_to_campaigns_tables" => {
1193                let key_pos_0 = definition.column_position_by_name("loading_quote").unwrap_or_default();
1194                let key_pos_1 = definition.column_position_by_name("campaign").unwrap_or_default();
1195
1196                keys.extend(self.data()
1197                    .iter()
1198                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string())
1199                    .collect::<Vec<_>>()
1200                );
1201            }
1202
1203            // Order reversed vs dave schemas: event + optional_campaign_key + culture + optional_subculture.
1204            "message_event_strings_tables" => {
1205                let key_pos_0 = definition.column_position_by_name("event").unwrap_or_default();
1206                let key_pos_1 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1207                let key_pos_2 = definition.column_position_by_name("culture").unwrap_or_default();
1208                let key_pos_3 = definition.column_position_by_name("optional_subculture").unwrap_or_default();
1209
1210                keys.extend(self.data()
1211                    .iter()
1212                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string())
1213                    .collect::<Vec<_>>()
1214                );
1215            }
1216
1217            // Uses 1 and 0 for bools: template_key + battle_type + is_defender ? 1 : 0.
1218            "mp_force_gen_template_junctions_tables" => {
1219                let key_pos_0 = definition.column_position_by_name("template_key").unwrap_or_default();
1220                let key_pos_1 = definition.column_position_by_name("battle_type").unwrap_or_default();
1221                let key_pos_2 = definition.column_position_by_name("is_defender").unwrap_or_default();
1222
1223                keys.extend(self.data()
1224                    .iter()
1225                    .map(|x| {
1226                        let is_defender = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
1227                        x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + is_defender
1228                    })
1229                    .collect::<Vec<_>>()
1230                );
1231            }
1232
1233            // Uses ; for concatenating keys: initiative_record + ";" + strength.
1234            "provincial_initiative_strength_levels_tables" => {
1235                let key_pos_0 = definition.column_position_by_name("initiative_record").unwrap_or_default();
1236                let key_pos_1 = definition.column_position_by_name("strength").unwrap_or_default();
1237
1238                keys.extend(self.data()
1239                    .iter()
1240                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1241                    .collect::<Vec<_>>()
1242                );
1243            }
1244
1245            // Order reversed vs dave schemas: province + region.
1246            "region_to_province_junctions_tables" => {
1247                let key_pos_0 = definition.column_position_by_name("province").unwrap_or_default();
1248                let key_pos_1 = definition.column_position_by_name("region").unwrap_or_default();
1249
1250                keys.extend(self.data()
1251                    .iter()
1252                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1253                    .collect::<Vec<_>>()
1254                );
1255            }
1256
1257            // Uses | for concatenating keys: payload + "|" + agent_record.
1258            "ritual_payload_change_agent_capacities_tables" => {
1259                let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
1260                let key_pos_1 = definition.column_position_by_name("agent_record").unwrap_or_default();
1261
1262                keys.extend(self.data()
1263                    .iter()
1264                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1265                    .collect::<Vec<_>>()
1266                );
1267            }
1268
1269            // Uses | for concatenating keys: payload + "|" + unit_record.
1270            "ritual_payload_change_unit_capacities_tables" => {
1271                let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
1272                let key_pos_1 = definition.column_position_by_name("unit_record").unwrap_or_default();
1273
1274                keys.extend(self.data()
1275                    .iter()
1276                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1277                    .collect::<Vec<_>>()
1278                );
1279            }
1280
1281            // Uses | for concatenating keys: slot_set_type + "|" + feature.
1282            "slot_set_type_feature_junctions_tables" => {
1283                let key_pos_0 = definition.column_position_by_name("slot_set_type").unwrap_or_default();
1284                let key_pos_1 = definition.column_position_by_name("feature").unwrap_or_default();
1285
1286                keys.extend(self.data()
1287                    .iter()
1288                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1289                    .collect::<Vec<_>>()
1290                );
1291            }
1292
1293            // Uses : for concatenating keys: special_ability + ":" + phase + ":" + order.
1294            "special_ability_to_special_ability_phase_junctions_tables" => {
1295                let key_pos_0 = definition.column_position_by_name("special_ability").unwrap_or_default();
1296                let key_pos_1 = definition.column_position_by_name("phase").unwrap_or_default();
1297                let key_pos_2 = definition.column_position_by_name("order").unwrap_or_default();
1298
1299                keys.extend(self.data()
1300                    .iter()
1301                    .map(|x| x[key_pos_0].data_to_string().to_string() + ":" + &x[key_pos_1].data_to_string() + ":" + &x[key_pos_2].data_to_string())
1302                    .collect::<Vec<_>>()
1303                );
1304            }
1305
1306            // Order reversed vs dave schemas: campaign + id + region + slot_template + slot_type.
1307            "start_pos_region_slot_templates_tables" => {
1308                let key_pos_0 = definition.column_position_by_name("campaign").unwrap_or_default();
1309                let key_pos_1 = definition.column_position_by_name("id").unwrap_or_default();
1310                let key_pos_2 = definition.column_position_by_name("region").unwrap_or_default();
1311                let key_pos_3 = definition.column_position_by_name("slot_template").unwrap_or_default();
1312                let key_pos_4 = definition.column_position_by_name("slot_type").unwrap_or_default();
1313
1314                keys.extend(self.data()
1315                    .iter()
1316                    .map(|x|
1317                        x[key_pos_0].data_to_string().to_string() +
1318                        &x[key_pos_1].data_to_string() +
1319                        &x[key_pos_2].data_to_string() +
1320                        &x[key_pos_3].data_to_string() +
1321                        &x[key_pos_4].data_to_string()
1322                    )
1323                    .collect::<Vec<_>>()
1324                );
1325            }
1326
1327            // Order reversed vs dave schemas: faction + technology.
1328            "start_pos_technologies_tables" => {
1329                let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
1330                let key_pos_1 = definition.column_position_by_name("technology").unwrap_or_default();
1331
1332                keys.extend(self.data()
1333                    .iter()
1334                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1335                    .collect::<Vec<_>>()
1336                );
1337            }
1338
1339            // Uses custom format and ; for concatenating some of the keys: tax_name + ";" + effect + ";" + optional_campaign_key + ";" + optional_difficulty_level + ai_only ? 1 : 0.
1340            "taxes_effects_jct_tables" => {
1341                let key_pos_0 = definition.column_position_by_name("tax_name").unwrap_or_default();
1342                let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
1343                let key_pos_2 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1344                let key_pos_3 = definition.column_position_by_name("optional_difficulty_level").unwrap_or_default();
1345                let key_pos_4 = definition.column_position_by_name("ai_only").unwrap_or_default();
1346
1347                keys.extend(self.data()
1348                    .iter()
1349                    .map(|x| {
1350                        let ai_only = if x[key_pos_4].data_to_string() == "true" { "1" } else { "0" };
1351                        x[key_pos_0].data_to_string().to_string() + ";" +
1352                        &x[key_pos_1].data_to_string() + ";" +
1353                        &x[key_pos_2].data_to_string() + ";" +
1354                        &x[key_pos_3].data_to_string() + ai_only
1355                    })
1356                    .collect::<Vec<_>>()
1357                );
1358            }
1359
1360            // Uses _ for concatenating keys: hex_id + "_" + category.
1361            "ui_purchasable_effects_to_hex_ids_tables" => {
1362                let key_pos_0 = definition.column_position_by_name("hex_id").unwrap_or_default();
1363                let key_pos_1 = definition.column_position_by_name("category").unwrap_or_default();
1364
1365                keys.extend(self.data()
1366                    .iter()
1367                    .map(|x| x[key_pos_0].data_to_string().to_string() + "_" + &x[key_pos_1].data_to_string())
1368                    .collect::<Vec<_>>()
1369                );
1370            }
1371
1372            // Uses custom formatting and : for concatenating keys: unit + ":" + faction + ":" + (general_unit ? "1" : "0").
1373            "units_custom_battle_permissions_tables" => {
1374                let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
1375                let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
1376                let key_pos_2 = definition.column_position_by_name("general_unit").unwrap_or_default();
1377
1378                keys.extend(self.data()
1379                    .iter()
1380                    .map(|x| {
1381                        let general_unit = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
1382                        x[key_pos_0].data_to_string().to_string() + ":" + &x[key_pos_1].data_to_string() + ":" + general_unit
1383                    })
1384                    .collect::<Vec<_>>()
1385                );
1386            }
1387
1388            // It includes the level_to_unlock string two times: category + level_to_unlock + level_to_unlock.
1389            "workshop_categories_progress_levels_tables" => {
1390                let key_pos_0 = definition.column_position_by_name("category").unwrap_or_default();
1391                let key_pos_1 = definition.column_position_by_name("level_to_unlock").unwrap_or_default();
1392
1393                keys.extend(self.data()
1394                    .iter()
1395                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + &x[key_pos_1].data_to_string())
1396                    .collect::<Vec<_>>()
1397                );
1398            }
1399
1400            // For anything else (single-keys and keys that follow the schemas without weird behavior), use the twad order.
1401            _ => {
1402                let key_cols = definition.key_column_positions_by_ca_order();
1403                keys.extend(self.data()
1404                    .iter()
1405                    .map(|x| key_cols.iter()
1406                        .map(|y| x[*y].data_to_string())
1407                        .join("")
1408                    )
1409                    .collect::<Vec<_>>()
1410                );
1411            }
1412        }
1413    }
1414}
1415
1416/// Implementation to create a `DB` from a `Table`.
1417impl From<TableInMemory> for DB {
1418    fn from(table: TableInMemory) -> Self {
1419        Self {
1420            mysterious_byte: true,
1421            guid: Uuid::new_v4().to_string(),
1422            table,
1423        }
1424    }
1425}