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    /// Overwrites this table's GUID with the provided one.
431    pub fn set_guid(&mut self, guid: String) {
432        self.guid = guid;
433    }
434
435    /// Creates a new row with default values from the table definition.
436    pub fn new_row(&self) -> Vec<DecodedData> {
437        self.table().new_row()
438    }
439
440    /// Returns a test definition with all field types for unit testing.
441    #[cfg(test)]
442    pub fn test_definition() -> Definition {
443        let mut definition = Definition::new(-100, None);
444        let mut fields = vec![];
445
446        fields.push(Field { name: "bool".to_owned(), field_type: FieldType::Boolean, default_value: Some("true".to_string()), ..Default::default() });
447        fields.push(Field { name: "f32".to_owned(), field_type: FieldType::F32, default_value: Some("1.0".to_string()), ..Default::default() });
448        fields.push(Field { name: "f64".to_owned(), field_type: FieldType::F64, default_value: Some("2.0".to_string()), ..Default::default() });
449        fields.push(Field { name: "i16".to_owned(), field_type: FieldType::I16, default_value: Some("3".to_string()), ..Default::default() });
450        fields.push(Field { name: "i32".to_owned(), field_type: FieldType::I32, default_value: Some("4".to_string()), ..Default::default() });
451        fields.push(Field { name: "i64".to_owned(), field_type: FieldType::I64, default_value: Some("5".to_string()), ..Default::default() });
452        fields.push(Field { name: "colour".to_owned(), field_type: FieldType::ColourRGB, default_value: Some("ABCDEF".to_string()), ..Default::default() });
453        fields.push(Field { name: "stringu8".to_owned(), field_type: FieldType::StringU8, default_value: Some("AAAA".to_string()), ..Default::default() });
454        fields.push(Field { name: "stringu16".to_owned(), field_type: FieldType::StringU16, default_value: Some("BBBB".to_string()), ..Default::default() });
455        fields.push(Field { name: "optionali16".to_owned(), field_type: FieldType::OptionalI16, default_value: Some("3".to_string()), ..Default::default() });
456        fields.push(Field { name: "optionali32".to_owned(), field_type: FieldType::OptionalI32, default_value: Some("4".to_string()), ..Default::default() });
457        fields.push(Field { name: "optionali64".to_owned(), field_type: FieldType::OptionalI64, default_value: Some("5".to_string()), ..Default::default() });
458        fields.push(Field { name: "optionalstringu8".to_owned(), field_type: FieldType::OptionalStringU8, default_value: Some("Opt".to_string()), ..Default::default() });
459        fields.push(Field { name: "optionalstringu16".to_owned(), field_type: FieldType::OptionalStringU16, default_value: Some("Opt".to_string()), ..Default::default() });
460        fields.push(Field { name: "sequenceu16".to_owned(), field_type: FieldType::SequenceU16(Box::new(Definition::new(-100, None))), ..Default::default() });
461        fields.push(Field { name: "sequenceu32".to_owned(), field_type: FieldType::SequenceU32(Box::new(Definition::new(-100, None))), ..Default::default() });
462
463        // Special fields that use postprocessing.
464        fields.push(Field {
465            name: "merged_colours_1_r".to_owned(),
466            field_type: FieldType::I32,
467            default_value: Some("AB".to_string()),
468            is_part_of_colour: Some(0),
469            ..Default::default()
470        });
471        fields.push(Field {
472            name: "merged_colours_1_g".to_owned(),
473            field_type: FieldType::I32,
474            default_value: Some("CD".to_string()),
475            is_part_of_colour: Some(0),
476            ..Default::default()
477        });
478        fields.push(Field {
479            name: "merged_colours_1_b".to_owned(),
480            field_type: FieldType::I32,
481            default_value: Some("EF".to_string()),
482            is_part_of_colour: Some(0),
483            ..Default::default()
484        });
485        fields.push(Field {
486            name: "bitwise_values".to_owned(),
487            field_type: FieldType::I32,
488            default_value: Some("4".to_string()),
489            is_bitwise: 5,
490            ..Default::default()
491        });
492        fields.push(Field {
493            name: "enum_values".to_owned(),
494            field_type: FieldType::I32,
495            default_value: Some("8".to_string()),
496            enum_values: {
497                let mut bt = BTreeMap::new();
498                bt.insert(0, "test0".to_owned());
499                bt.insert(1, "test1".to_owned());
500                bt.insert(2, "test2".to_owned());
501                bt.insert(3, "test3".to_owned());
502                bt
503            },
504            ..Default::default()
505        });
506
507        fields.push(Field {
508            name: "merged_colours_2_r".to_owned(),
509            field_type: FieldType::I32,
510            default_value: Some("AB".to_string()),
511            is_part_of_colour: Some(1),
512            ..Default::default()
513        });
514        fields.push(Field {
515            name: "merged_colours_2_g".to_owned(),
516            field_type: FieldType::I32,
517            default_value: Some("CD".to_string()),
518            is_part_of_colour: Some(1),
519            ..Default::default()
520        });
521        fields.push(Field {
522            name: "merged_colours_2_b".to_owned(),
523            field_type: FieldType::I32,
524            default_value: Some("EF".to_string()),
525            is_part_of_colour: Some(1),
526            ..Default::default()
527        });
528
529        // TODO: add combined colour columns for testing.
530
531        definition.set_fields(fields);
532        definition
533    }
534
535    /// Returns the column index for a given column name, or `None` if not found.
536    pub fn column_position_by_name(&self, column_name: &str) -> Option<usize> {
537        self.table.column_position_by_name(column_name)
538    }
539
540    /// Returns the number of rows in the table.
541    pub fn len(&self) -> usize {
542        self.table.len()
543    }
544
545    /// Returns `true` if the table has no rows.
546    pub fn is_empty(&self) -> bool {
547        self.table.is_empty()
548    }
549
550    /// Replaces the table definition and migrates existing data to match.
551    ///
552    /// Use this to update tables to a newer schema version. Data is converted
553    /// between compatible types where possible.
554    pub fn set_definition(&mut self, new_definition: &Definition) {
555        self.table.set_definition(new_definition);
556    }
557
558    /// Alias for [`set_definition`](Self::set_definition).
559    pub fn update(&mut self, new_definition: &Definition) {
560        self.set_definition(new_definition)
561    }
562
563    /// Performs a cascade update of a value across all referencing tables in a Pack.
564    ///
565    /// When a key field value is changed, this function finds all tables that reference
566    /// that field and updates them accordingly. It also updates corresponding Loc entries
567    /// if the edited field affects localisation keys.
568    ///
569    /// # Arguments
570    ///
571    /// * `pack` - The Pack to search and update.
572    /// * `schema` - Schema containing table definitions and reference information.
573    /// * `table_name` - Name of the source table (e.g., `"units_tables"`).
574    /// * `field` - The field being edited.
575    /// * `definition` - Definition of the source table.
576    /// * `value_before` - Original value being replaced.
577    /// * `value_after` - New value to set.
578    ///
579    /// # Returns
580    ///
581    /// List of paths where references were found and updated.
582    pub fn cascade_edition(pack: &mut Pack, schema: &Schema, table_name: &str, field: &Field, definition: &Definition, value_before: &str, value_after: &str) -> Vec<ContainerPath> {
583
584        // So, how does this work:
585        // - 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.
586        // - Second, we need to calculate all related loc fields corresponding to the edited fiels.
587        // - Third, we edit the table entries.
588        // - Fourth, we edit the loc entries.
589        let mut edited_paths = vec![];
590
591        // If we're not changing anything, don't bother performing an edition.
592        if value_before == value_after {
593            return vec![];
594        }
595
596        // Just in case we're in a reference field, find the source, and trigger the edition from there.
597        let mut definition = definition.clone();
598        let patches = Some(definition.patches().clone());
599        let mut field = field.clone();
600        let mut table_name = table_name.to_owned();
601        while let Some((ref_table, ref_column)) = field.is_reference(patches.as_ref()) {
602            let ref_table_name = format!("{ref_table}_tables");
603            let table_folder = format!("db/{ref_table_name}");
604            let parent_files = pack.files_by_type_and_paths_mut(&[FileType::DB], &[ContainerPath::Folder(table_folder.to_owned())], true);
605            if !parent_files.is_empty() {
606                if let Ok(RFileDecoded::DB(table)) = parent_files[0].decoded() {
607                    if let Some(index) = table.definition().column_position_by_name(&ref_column) {
608                        definition = table.definition().clone();
609                        field = definition.fields_processed()[index].clone();
610                        table_name = table.table_name().to_owned();
611                        continue;
612                    }
613                }
614            }
615
616            break;
617        }
618
619        // Get the tables/rows that need to be edited.
620        let fields_processed = definition.fields_processed();
621        let fields_localised = definition.localised_fields();
622        let (mut ref_table_data, _) = schema.tables_and_columns_referencing_our_own(&table_name, field.name(), &fields_processed, fields_localised);
623
624        // Add the source table and column to the list to edit.
625        ref_table_data.insert(table_name, vec![field.name().to_owned()]);
626
627        let container_paths = ref_table_data.keys().map(|ref_table_name| ContainerPath::Folder("db/".to_owned() + ref_table_name)).collect::<Vec<_>>();
628        let mut files = pack.files_by_paths_mut(&container_paths, true);
629        let mut loc_keys: Vec<(String, String)> = vec![];
630
631        for file in files.iter_mut() {
632            let path = file.path_in_container();
633            if let Ok(RFileDecoded::DB(table)) = file.decoded_mut() {
634                let fields_processed = table.definition().fields_processed();
635                let fields_localised = table.definition().localised_fields().to_vec();
636                let localised_order = table.definition().localised_key_order().to_vec();
637                let patches = table.definition().patches().clone();
638                let table_name = table.table_name().to_owned();
639                let table_name_no_tables = table.table_name_without_tables();
640                let table_data = table.data_mut();
641
642                let mut keys_edited = vec![];
643
644                // Find the column to edit within the table.
645                let column_indexes = fields_processed.iter()
646                    .enumerate()
647                    .filter_map(|(index, field)| if ref_table_data[&table_name].iter().any(|name| name == field.name()) { Some(index) } else { None })
648                    .collect::<Vec<usize>>();
649
650                // Then, go through all the rows and perform the edits.
651                for row in table_data.iter_mut() {
652                    for column in &column_indexes {
653
654                        // TODO: FIX THIS SHIT. It duplicates ALL DATA IN ALL TABLES CHECK.
655                        let row_copy = row.to_vec();
656
657                        if let Some(field_data) = row.get_mut(*column) {
658                            match field_data {
659                                DecodedData::StringU8(field_data) |
660                                DecodedData::StringU16(field_data) |
661                                DecodedData::OptionalStringU8(field_data) |
662                                DecodedData::OptionalStringU16(field_data) => {
663
664                                    // Only edit exact matches.
665                                    if field_data == value_before {
666                                        let mut locs_edited = vec![];
667
668                                        // If it's a key, calculate the relevant before and after loc keys.
669                                        let is_key = fields_processed[*column].is_key(Some(&patches));
670                                        if is_key {
671                                            for loc_field in &fields_localised {
672                                                let loc_key = localised_order.iter().map(|pos| row_copy[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
673                                                locs_edited.push(format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key));
674                                            }
675                                        }
676
677                                        *field_data = value_after.to_owned();
678
679                                        if !locs_edited.is_empty() {
680                                            for (index, loc_field) in fields_localised.iter().enumerate() {
681                                                if let Some(key_old) = locs_edited.get(index) {
682                                                    let loc_key = localised_order.iter().map(|pos| row[*pos as usize].data_to_string()).collect::<Vec<_>>().join("");
683                                                    let key_new = format!("{}_{}_{}", table_name_no_tables, loc_field.name(), loc_key);
684                                                    keys_edited.push((key_old.to_owned(), key_new.to_owned()))
685                                                }
686                                            }
687                                        }
688
689                                        if !edited_paths.contains(&path) {
690                                            edited_paths.push(path.clone());
691                                        }
692                                    }
693                                }
694                                _ => continue
695                            }
696                        }
697                    }
698                }
699
700                // If we edited a key field in the table, check if we need to edit any relevant loc field.
701                if !keys_edited.is_empty() {
702                    loc_keys.append(&mut keys_edited);
703                }
704            }
705        }
706
707        // Now, we find and replace all the loc keys we have to change.
708        let mut loc_files = pack.files_by_type_mut(&[FileType::Loc]);
709        for file in &mut loc_files {
710            let path = file.path_in_container();
711            if let Ok(RFileDecoded::Loc(data)) = file.decoded_mut() {
712                let data = data.data_mut();
713                for row in data.iter_mut() {
714                    if let Some(
715                        DecodedData::StringU8(field_data) |
716                        DecodedData::StringU16(field_data) |
717                        DecodedData::OptionalStringU8(field_data) |
718                        DecodedData::OptionalStringU16(field_data)
719                    ) = row.get_mut(0) {
720                        for (key_old, key_new) in &loc_keys {
721                            if field_data == key_old {
722                                *field_data = key_new.to_owned();
723
724                                if !edited_paths.contains(&path) {
725                                    edited_paths.push(path.clone());
726                                }
727                            }
728                        }
729                    }
730                }
731            }
732        }
733
734        edited_paths
735    }
736
737    /// Merges multiple DB tables into a single new table.
738    ///
739    /// Combines all rows from the source tables. The first table's definition and
740    /// patches are used for the merged result. All source tables are converted to
741    /// match this definition before merging.
742    ///
743    /// # Errors
744    ///
745    /// Returns an error if:
746    /// - Tables have different names (can't merge `units_tables` with `buildings_tables`)
747    /// - Fewer than 2 tables are provided
748    pub fn merge(sources: &[&Self]) -> Result<Self> {
749
750        let table_names = sources.iter().map(|file| file.table_name()).collect::<HashSet<_>>();
751        if table_names.len() > 1 {
752            return Err(RLibError::RFileMergeTablesDifferentNames);
753        }
754
755        if sources.len() < 2 {
756            return Err(RLibError::RFileMergeTablesNotEnoughTablesProvided);
757        }
758
759        let mut new_table = Self::new(sources[0].definition(), Some(sources[0].patches()), sources[0].table_name());
760        let sources = sources.par_iter()
761            .map(|table| {
762                let mut table = table.table().clone();
763                table.set_definition(new_table.definition());
764                table
765            })
766            .collect::<Vec<_>>();
767
768        let new_data = sources.par_iter()
769            .map(|table| table.data().to_vec())
770            .flatten()
771            .collect::<Vec<_>>();
772        new_table.set_data(&new_data)?;
773
774        Ok(new_table)
775    }
776
777    /// Imports a DB table from TSV (tab-separated values) format.
778    ///
779    /// # Arguments
780    ///
781    /// * `records` - CSV reader iterator over TSV records.
782    /// * `field_order` - Mapping of column positions to field names.
783    /// * `schema` - Schema containing the table definition.
784    /// * `table_name` - Name of the table (e.g., `"units_tables"`).
785    /// * `table_version` - Version of the table definition to use.
786    ///
787    /// # Errors
788    ///
789    /// Returns an error if no matching definition is found in the schema.
790    pub fn tsv_import(records: StringRecordsIter<File>, field_order: &HashMap<u32, String>, schema: &Schema, table_name: &str, table_version: i32) -> Result<Self> {
791        let definition = schema.definition_by_name_and_version(table_name, table_version).ok_or(RLibError::DecodingDBNoDefinitionsFound)?;
792        let definition_patch = schema.patches_for_table(table_name);
793        let table = TableInMemory::tsv_import(records, definition, field_order, table_name, definition_patch)?;
794        let db = DB::from(table);
795        Ok(db)
796    }
797
798    /// Exports the DB table to TSV (tab-separated values) format.
799    ///
800    /// # Arguments
801    ///
802    /// * `writer` - CSV writer for the output file.
803    /// * `table_path` - Path used in the TSV metadata header.
804    /// * `keys_first` - If `true`, key columns are written before non-key columns.
805    pub fn tsv_export(&self, writer: &mut Writer<File>, table_path: &str, keys_first: bool) -> Result<()> {
806        self.table.tsv_export(writer, table_path, keys_first)
807    }
808
809    /// Returns `true` if data was modified during decoding (e.g., invalid values corrected).
810    pub fn altered(&self) -> bool {
811        *self.table.altered()
812    }
813
814    /// Generates combined primary keys to populate the `twad_key_deletes` table.
815    ///
816    /// Different tables use different key concatenation rules. This function handles
817    /// the table-specific key format for each known table type.
818    pub fn generate_twad_key_deletes_keys(&self, keys: &mut HashSet<String>) {
819        let definition = self.definition();
820        match self.table_name() {
821
822            // Order reversed vs dave schemas: agent + attribute.
823            "agent_to_agent_attributes_tables" => {
824                let key_pos_0 = definition.column_position_by_name("agent").unwrap_or_default();
825                let key_pos_1 = definition.column_position_by_name("attribute").unwrap_or_default();
826
827                keys.extend(self.data()
828                    .iter()
829                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
830                    .collect::<Vec<_>>()
831                );
832            }
833
834            // Uses ; for concatenating keys: attacker + ";" + defender.
835            "animation_set_prebattle_group_view_configurations_tables" => {
836                let key_pos_0 = definition.column_position_by_name("attacker").unwrap_or_default();
837                let key_pos_1 = definition.column_position_by_name("defender").unwrap_or_default();
838
839                keys.extend(self.data()
840                    .iter()
841                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
842                    .collect::<Vec<_>>()
843                );
844            }
845
846            // Uses " | ", with whitespace, for concatenating keys: armory_item + " | "  + variant.
847            "armory_item_variants_tables" => {
848                let key_pos_0 = definition.column_position_by_name("armory_item").unwrap_or_default();
849                let key_pos_1 = definition.column_position_by_name("variant").unwrap_or_default();
850
851                keys.extend(self.data()
852                    .iter()
853                    .map(|x| x[key_pos_0].data_to_string().to_string() + " | " + &x[key_pos_1].data_to_string())
854                    .collect::<Vec<_>>()
855                );
856            }
857
858            // It includes the item_type string two times: item_type + currency_type + item_type.
859            "battle_currency_deployables_cost_values_tables" => {
860                let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
861                let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
862
863                keys.extend(self.data()
864                    .iter()
865                    .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())
866                    .collect::<Vec<_>>()
867                );
868            }
869
870            // It includes the item_type string two times: item_type + currency_type + item_type.
871            "battle_currency_units_cost_values_tables" => {
872                let key_pos_0 = definition.column_position_by_name("item_type").unwrap_or_default();
873                let key_pos_1 = definition.column_position_by_name("currency_type").unwrap_or_default();
874
875                keys.extend(self.data()
876                    .iter()
877                    .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())
878                    .collect::<Vec<_>>()
879                );
880            }
881
882            // It includes the army_name string two times: army_name + siege_item + army_name.
883            "battle_set_pieces_siege_items_tables" => {
884                let key_pos_0 = definition.column_position_by_name("army_name").unwrap_or_default();
885                let key_pos_1 = definition.column_position_by_name("siege_item").unwrap_or_default();
886
887                keys.extend(self.data()
888                    .iter()
889                    .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())
890                    .collect::<Vec<_>>()
891                );
892            }
893
894            // Uses ; for concatenating keys: distribution_profile_key + ";" + agent_type_key.
895            "cai_agent_type_distribution_profile_junctions_tables" => {
896                let key_pos_0 = definition.column_position_by_name("distribution_profile_key").unwrap_or_default();
897                let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
898
899                keys.extend(self.data()
900                    .iter()
901                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
902                    .collect::<Vec<_>>()
903                );
904            }
905
906            // Uses ; for concatenating keys: recruitment_profile_key + ";" + agent_type_key.
907            "cai_agent_type_recruitment_profile_junctions_tables" => {
908                let key_pos_0 = definition.column_position_by_name("recruitment_profile_key").unwrap_or_default();
909                let key_pos_1 = definition.column_position_by_name("agent_type_key").unwrap_or_default();
910
911                keys.extend(self.data()
912                    .iter()
913                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
914                    .collect::<Vec<_>>()
915                );
916            }
917
918            // Uses ; for concatenating keys: budget_allocation_key + ";" + budget_context_key + "; + budget_policy_key.
919            "cai_personalities_budget_allocation_policy_junctions_tables" => {
920                let key_pos_0 = definition.column_position_by_name("budget_allocation_key").unwrap_or_default();
921                let key_pos_1 = definition.column_position_by_name("budget_context_key").unwrap_or_default();
922                let key_pos_2 = definition.column_position_by_name("budget_policy_key").unwrap_or_default();
923
924                keys.extend(self.data()
925                    .iter()
926                    .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())
927                    .collect::<Vec<_>>()
928                );
929            }
930
931            // Uses ; for concatenating keys: building_key + ";" + policy_key.
932            "cai_personalities_construction_preference_policy_building_junctions_tables" => {
933                let key_pos_0 = definition.column_position_by_name("building_key").unwrap_or_default();
934                let key_pos_1 = definition.column_position_by_name("policy_key").unwrap_or_default();
935
936                keys.extend(self.data()
937                    .iter()
938                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
939                    .collect::<Vec<_>>()
940                );
941            }
942
943            // It includes the variable_group_key string two times: variable_group_key + variable_key + variable_group_key.
944            "cai_task_management_system_task_generator_variable_group_junctions_tables" => {
945                let key_pos_0 = definition.column_position_by_name("variable_group_key").unwrap_or_default();
946                let key_pos_1 = definition.column_position_by_name("variable_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() + &x[key_pos_0].data_to_string())
951                    .collect::<Vec<_>>()
952                );
953            }
954
955            // Uses ; for concatenating keys: manager + ";" + behaviour.
956            "campaign_ai_manager_behaviour_junctions_tables" => {
957                let key_pos_0 = definition.column_position_by_name("manager").unwrap_or_default();
958                let key_pos_1 = definition.column_position_by_name("behaviour").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            // Order reversed vs dave schemas: bmd_export_types + campaign_bmd_layer_group.
968            "campaign_bmd_layer_group_bmd_export_types_junctions_tables" => {
969                let key_pos_0 = definition.column_position_by_name("bmd_export_types").unwrap_or_default();
970                let key_pos_1 = definition.column_position_by_name("campaign_bmd_layer_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, and 1 and 0 for bools: campaign_difficulty_handicap + "-" + human ? 1 : 0 + "-" + effect + optional_campaign_key.
980            "campaign_difficulty_handicap_effects_tables" => {
981                let key_pos_0 = definition.column_position_by_name("campaign_difficulty_handicap").unwrap_or_default();
982                let key_pos_1 = definition.column_position_by_name("human").unwrap_or_default();
983                let key_pos_2 = definition.column_position_by_name("effect").unwrap_or_default();
984                let key_pos_3 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
985
986                keys.extend(self.data()
987                    .iter()
988                    .map(|x| {
989                        let human = if x[key_pos_1].data_to_string() == "true" { "1" } else { "0" };
990                        x[key_pos_0].data_to_string().to_string() + "-" + human + "-" + &x[key_pos_2].data_to_string() + &x[key_pos_3].data_to_string()
991                    })
992                    .collect::<Vec<_>>()
993                );
994            }
995
996            // Order reversed vs dave schemas: agent_key + campaign_effect_scope_key.
997            "campaign_effect_scope_agent_junctions_tables" => {
998                let key_pos_0 = definition.column_position_by_name("agent_key").unwrap_or_default();
999                let key_pos_1 = definition.column_position_by_name("campaign_effect_scope_key").unwrap_or_default();
1000
1001                keys.extend(self.data()
1002                    .iter()
1003                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1004                    .collect::<Vec<_>>()
1005                );
1006            }
1007
1008            // Uses ; for concatenating keys: feature + ";" + group.
1009            "campaign_features_tables" => {
1010                let key_pos_0 = definition.column_position_by_name("feature").unwrap_or_default();
1011                let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
1012
1013                keys.extend(self.data()
1014                    .iter()
1015                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1016                    .collect::<Vec<_>>()
1017                );
1018            }
1019
1020            // Uses ; for concatenating keys: marker_type + ";" + group.
1021            "campaign_markers_tables" => {
1022                let key_pos_0 = definition.column_position_by_name("marker_type").unwrap_or_default();
1023                let key_pos_1 = definition.column_position_by_name("group").unwrap_or_default();
1024
1025                keys.extend(self.data()
1026                    .iter()
1027                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1028                    .collect::<Vec<_>>()
1029                );
1030            }
1031
1032            // Uses custom formatting: faction_override.empty() ? unit : unit + "_" + faction_override.
1033            "campaign_mercenary_unit_character_level_restrictions_tables" => {
1034                let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
1035                let key_pos_1 = definition.column_position_by_name("faction_override").unwrap_or_default();
1036
1037                keys.extend(self.data()
1038                    .iter()
1039                    .map(|x| {
1040                        let fover = if x[key_pos_1].data_to_string().is_empty() {
1041                            "".to_owned()
1042                        } else {
1043                            "_".to_string() + &x[key_pos_1].data_to_string()
1044                        };
1045                        x[key_pos_0].data_to_string().to_string() + &fover
1046                    })
1047                    .collect::<Vec<_>>()
1048                );
1049            }
1050
1051            // It includes the id string two times: id + id.
1052            "campaign_movement_spline_materials_tables" => {
1053                let key_pos_0 = definition.column_position_by_name("id").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_0].data_to_string())
1058                    .collect::<Vec<_>>()
1059                );
1060            }
1061
1062            // Campaign doesn't exist in newer definitions: campaign + faction.
1063            "campaign_rogue_army_leaders_tables" => {
1064                let key_pos_0 = definition.column_position_by_name("campaign");
1065                let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
1066
1067                keys.extend(self.data()
1068                    .iter()
1069                    .map(|x| {
1070                        let camp = if let Some(y) = key_pos_0 { x[y].data_to_string().to_string() } else { "".to_owned() };
1071                        camp + &x[key_pos_1].data_to_string()
1072                    })
1073                    .collect::<Vec<_>>()
1074                );
1075            }
1076
1077            // Uses ; for concatenating keys: faction + ";" + difficulty_level.
1078            "campaign_rogue_army_setups_tables" => {
1079                let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
1080                let key_pos_1 = definition.column_position_by_name("difficulty_level").unwrap_or_default();
1081
1082                keys.extend(self.data()
1083                    .iter()
1084                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1085                    .collect::<Vec<_>>()
1086                );
1087            }
1088
1089            // Order custom vs dave schemas: variable_key + campaign_name + difficulty + campaign_type.
1090            "campaigns_campaign_variables_junctions_tables" => {
1091                let key_pos_0 = definition.column_position_by_name("variable_key").unwrap_or_default();
1092                let key_pos_1 = definition.column_position_by_name("campaign_name").unwrap_or_default();
1093                let key_pos_2 = definition.column_position_by_name("difficulty").unwrap_or_default();
1094                let key_pos_3 = definition.column_position_by_name("campaign_type").unwrap_or_default();
1095
1096                keys.extend(self.data()
1097                    .iter()
1098                    .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())
1099                    .collect::<Vec<_>>()
1100                );
1101            }
1102
1103            // It includes the tree_type string two times: tree_type + variable_key + tree_type.
1104            "campaign_tree_type_cultures_tables" => {
1105                let key_pos_0 = definition.column_position_by_name("tree_type").unwrap_or_default();
1106                let key_pos_1 = definition.column_position_by_name("culture").unwrap_or_default();
1107
1108                keys.extend(self.data()
1109                    .iter()
1110                    .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())
1111                    .collect::<Vec<_>>()
1112                );
1113            }
1114
1115            // Order reversed vs dave schemas: issuer_key + mission_key.
1116            "cdir_events_mission_issuer_junctions_tables" => {
1117                let key_pos_0 = definition.column_position_by_name("issuer_key").unwrap_or_default();
1118                let key_pos_1 = definition.column_position_by_name("mission_key").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())
1123                    .collect::<Vec<_>>()
1124                );
1125            }
1126
1127            // Uses custom formatting: (for_army ? (for_navy ? "navy" : "army") : agent_key) + skill_rank + optional_campaign_key.
1128            "character_experience_skill_tiers_tables" => {
1129                let key_pos_0 = definition.column_position_by_name("for_army").unwrap_or_default();
1130                let key_pos_1 = definition.column_position_by_name("for_navy").unwrap_or_default();
1131                let key_pos_2 = definition.column_position_by_name("agent_key").unwrap_or_default();
1132                let key_pos_3 = definition.column_position_by_name("skill_rank").unwrap_or_default();
1133                let key_pos_4 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1134
1135                keys.extend(self.data()
1136                    .iter()
1137                    .map(|x| {
1138                        let mut ckey = String::new();
1139                        if x[key_pos_0].data_to_string() == "true" {
1140                            if x[key_pos_1].data_to_string() == "true" {
1141                                ckey.push_str("navy");
1142                            } else {
1143                                ckey.push_str("army");
1144                            }
1145                        } else {
1146                            ckey.push_str(&x[key_pos_2].data_to_string());
1147                        }
1148
1149                        ckey + &x[key_pos_3].data_to_string() + &x[key_pos_4].data_to_string()
1150                    })
1151                    .collect::<Vec<_>>()
1152                );
1153            }
1154
1155            // Order reversed vs dave schemas: battle_animations_table + culture_pack.
1156            "culture_to_battle_animation_tables_tables" => {
1157                let key_pos_0 = definition.column_position_by_name("battle_animations_table").unwrap_or_default();
1158                let key_pos_1 = definition.column_position_by_name("culture_pack").unwrap_or_default();
1159
1160                keys.extend(self.data()
1161                    .iter()
1162                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1163                    .collect::<Vec<_>>()
1164                );
1165            }
1166
1167            // Order reversed vs dave schemas: effect + action_results_additional_outcome_record + bonus_value_id.
1168            "effect_bonus_value_id_action_results_additional_outcomes_junctions_tables" => {
1169                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1170                let key_pos_1 = definition.column_position_by_name("action_results_additional_outcome_record").unwrap_or_default();
1171                let key_pos_2 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1172
1173                keys.extend(self.data()
1174                    .iter()
1175                    .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())
1176                    .collect::<Vec<_>>()
1177                );
1178            }
1179
1180            // Order reversed vs dave schemas: effect + bonus_value + projectile.
1181            "effect_bonus_value_projectile_junctions_tables" => {
1182                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1183                let key_pos_1 = definition.column_position_by_name("bonus_value").unwrap_or_default();
1184                let key_pos_2 = definition.column_position_by_name("projectile").unwrap_or_default();
1185
1186                keys.extend(self.data()
1187                    .iter()
1188                    .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())
1189                    .collect::<Vec<_>>()
1190                );
1191            }
1192
1193            // Uses ; for concatenating keys: effect + ";" + bonus_value_id + ";" + unit_attribute.
1194            "effect_bonus_value_unit_attribute_junctions_tables" => {
1195                let key_pos_0 = definition.column_position_by_name("effect").unwrap_or_default();
1196                let key_pos_1 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1197                let key_pos_2 = definition.column_position_by_name("unit_attribute").unwrap_or_default();
1198
1199                keys.extend(self.data()
1200                    .iter()
1201                    .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())
1202                    .collect::<Vec<_>>()
1203                );
1204            }
1205
1206            // Order reversed vs dave schemas: bonus_value_id + effect + unit_record_key.
1207            "effect_bonus_value_unit_record_junctions_tables" => {
1208                let key_pos_0 = definition.column_position_by_name("bonus_value_id").unwrap_or_default();
1209                let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
1210                let key_pos_2 = definition.column_position_by_name("unit_record_key").unwrap_or_default();
1211
1212                keys.extend(self.data()
1213                    .iter()
1214                    .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())
1215                    .collect::<Vec<_>>()
1216                );
1217            }
1218
1219            // It includes the affected_stat string two times: ground_type + affected_stat + affected_group + affected_stat.
1220            "ground_type_to_stat_effects_tables" => {
1221                let key_pos_0 = definition.column_position_by_name("ground_type").unwrap_or_default();
1222                let key_pos_1 = definition.column_position_by_name("affected_stat").unwrap_or_default();
1223                let key_pos_2 = definition.column_position_by_name("affected_group").unwrap_or_default();
1224
1225                keys.extend(self.data()
1226                    .iter()
1227                    .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())
1228                    .collect::<Vec<_>>()
1229                );
1230            }
1231
1232            // Order reversed vs dave schemas: ability + land_unit.
1233            "land_units_to_unit_abilites_junctions_tables" => {
1234                let key_pos_0 = definition.column_position_by_name("ability").unwrap_or_default();
1235                let key_pos_1 = definition.column_position_by_name("land_unit").unwrap_or_default();
1236
1237                keys.extend(self.data()
1238                    .iter()
1239                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1240                    .collect::<Vec<_>>()
1241                );
1242            }
1243
1244            // It includes the affected_stat string three times: loading_quote + campaign + campaign + campaign.
1245            "loading_screen_quotes_to_campaigns_tables" => {
1246                let key_pos_0 = definition.column_position_by_name("loading_quote").unwrap_or_default();
1247                let key_pos_1 = definition.column_position_by_name("campaign").unwrap_or_default();
1248
1249                keys.extend(self.data()
1250                    .iter()
1251                    .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())
1252                    .collect::<Vec<_>>()
1253                );
1254            }
1255
1256            // Order reversed vs dave schemas: event + optional_campaign_key + culture + optional_subculture.
1257            "message_event_strings_tables" => {
1258                let key_pos_0 = definition.column_position_by_name("event").unwrap_or_default();
1259                let key_pos_1 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1260                let key_pos_2 = definition.column_position_by_name("culture").unwrap_or_default();
1261                let key_pos_3 = definition.column_position_by_name("optional_subculture").unwrap_or_default();
1262
1263                keys.extend(self.data()
1264                    .iter()
1265                    .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())
1266                    .collect::<Vec<_>>()
1267                );
1268            }
1269
1270            // Uses 1 and 0 for bools: template_key + battle_type + is_defender ? 1 : 0.
1271            "mp_force_gen_template_junctions_tables" => {
1272                let key_pos_0 = definition.column_position_by_name("template_key").unwrap_or_default();
1273                let key_pos_1 = definition.column_position_by_name("battle_type").unwrap_or_default();
1274                let key_pos_2 = definition.column_position_by_name("is_defender").unwrap_or_default();
1275
1276                keys.extend(self.data()
1277                    .iter()
1278                    .map(|x| {
1279                        let is_defender = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
1280                        x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string() + is_defender
1281                    })
1282                    .collect::<Vec<_>>()
1283                );
1284            }
1285
1286            // Uses ; for concatenating keys: initiative_record + ";" + strength.
1287            "provincial_initiative_strength_levels_tables" => {
1288                let key_pos_0 = definition.column_position_by_name("initiative_record").unwrap_or_default();
1289                let key_pos_1 = definition.column_position_by_name("strength").unwrap_or_default();
1290
1291                keys.extend(self.data()
1292                    .iter()
1293                    .map(|x| x[key_pos_0].data_to_string().to_string() + ";" + &x[key_pos_1].data_to_string())
1294                    .collect::<Vec<_>>()
1295                );
1296            }
1297
1298            // Order reversed vs dave schemas: province + region.
1299            "region_to_province_junctions_tables" => {
1300                let key_pos_0 = definition.column_position_by_name("province").unwrap_or_default();
1301                let key_pos_1 = definition.column_position_by_name("region").unwrap_or_default();
1302
1303                keys.extend(self.data()
1304                    .iter()
1305                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1306                    .collect::<Vec<_>>()
1307                );
1308            }
1309
1310            // Uses | for concatenating keys: payload + "|" + agent_record.
1311            "ritual_payload_change_agent_capacities_tables" => {
1312                let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
1313                let key_pos_1 = definition.column_position_by_name("agent_record").unwrap_or_default();
1314
1315                keys.extend(self.data()
1316                    .iter()
1317                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1318                    .collect::<Vec<_>>()
1319                );
1320            }
1321
1322            // Uses | for concatenating keys: payload + "|" + unit_record.
1323            "ritual_payload_change_unit_capacities_tables" => {
1324                let key_pos_0 = definition.column_position_by_name("payload").unwrap_or_default();
1325                let key_pos_1 = definition.column_position_by_name("unit_record").unwrap_or_default();
1326
1327                keys.extend(self.data()
1328                    .iter()
1329                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1330                    .collect::<Vec<_>>()
1331                );
1332            }
1333
1334            // Uses | for concatenating keys: slot_set_type + "|" + feature.
1335            "slot_set_type_feature_junctions_tables" => {
1336                let key_pos_0 = definition.column_position_by_name("slot_set_type").unwrap_or_default();
1337                let key_pos_1 = definition.column_position_by_name("feature").unwrap_or_default();
1338
1339                keys.extend(self.data()
1340                    .iter()
1341                    .map(|x| x[key_pos_0].data_to_string().to_string() + "|" + &x[key_pos_1].data_to_string())
1342                    .collect::<Vec<_>>()
1343                );
1344            }
1345
1346            // Uses : for concatenating keys: special_ability + ":" + phase + ":" + order.
1347            "special_ability_to_special_ability_phase_junctions_tables" => {
1348                let key_pos_0 = definition.column_position_by_name("special_ability").unwrap_or_default();
1349                let key_pos_1 = definition.column_position_by_name("phase").unwrap_or_default();
1350                let key_pos_2 = definition.column_position_by_name("order").unwrap_or_default();
1351
1352                keys.extend(self.data()
1353                    .iter()
1354                    .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())
1355                    .collect::<Vec<_>>()
1356                );
1357            }
1358
1359            // Order reversed vs dave schemas: campaign + id + region + slot_template + slot_type.
1360            "start_pos_region_slot_templates_tables" => {
1361                let key_pos_0 = definition.column_position_by_name("campaign").unwrap_or_default();
1362                let key_pos_1 = definition.column_position_by_name("id").unwrap_or_default();
1363                let key_pos_2 = definition.column_position_by_name("region").unwrap_or_default();
1364                let key_pos_3 = definition.column_position_by_name("slot_template").unwrap_or_default();
1365                let key_pos_4 = definition.column_position_by_name("slot_type").unwrap_or_default();
1366
1367                keys.extend(self.data()
1368                    .iter()
1369                    .map(|x|
1370                        x[key_pos_0].data_to_string().to_string() +
1371                        &x[key_pos_1].data_to_string() +
1372                        &x[key_pos_2].data_to_string() +
1373                        &x[key_pos_3].data_to_string() +
1374                        &x[key_pos_4].data_to_string()
1375                    )
1376                    .collect::<Vec<_>>()
1377                );
1378            }
1379
1380            // Order reversed vs dave schemas: faction + technology.
1381            "start_pos_technologies_tables" => {
1382                let key_pos_0 = definition.column_position_by_name("faction").unwrap_or_default();
1383                let key_pos_1 = definition.column_position_by_name("technology").unwrap_or_default();
1384
1385                keys.extend(self.data()
1386                    .iter()
1387                    .map(|x| x[key_pos_0].data_to_string().to_string() + &x[key_pos_1].data_to_string())
1388                    .collect::<Vec<_>>()
1389                );
1390            }
1391
1392            // Uses custom format and ; for concatenating some of the keys: tax_name + ";" + effect + ";" + optional_campaign_key + ";" + optional_difficulty_level + ai_only ? 1 : 0.
1393            "taxes_effects_jct_tables" => {
1394                let key_pos_0 = definition.column_position_by_name("tax_name").unwrap_or_default();
1395                let key_pos_1 = definition.column_position_by_name("effect").unwrap_or_default();
1396                let key_pos_2 = definition.column_position_by_name("optional_campaign_key").unwrap_or_default();
1397                let key_pos_3 = definition.column_position_by_name("optional_difficulty_level").unwrap_or_default();
1398                let key_pos_4 = definition.column_position_by_name("ai_only").unwrap_or_default();
1399
1400                keys.extend(self.data()
1401                    .iter()
1402                    .map(|x| {
1403                        let ai_only = if x[key_pos_4].data_to_string() == "true" { "1" } else { "0" };
1404                        x[key_pos_0].data_to_string().to_string() + ";" +
1405                        &x[key_pos_1].data_to_string() + ";" +
1406                        &x[key_pos_2].data_to_string() + ";" +
1407                        &x[key_pos_3].data_to_string() + ai_only
1408                    })
1409                    .collect::<Vec<_>>()
1410                );
1411            }
1412
1413            // Uses _ for concatenating keys: hex_id + "_" + category.
1414            "ui_purchasable_effects_to_hex_ids_tables" => {
1415                let key_pos_0 = definition.column_position_by_name("hex_id").unwrap_or_default();
1416                let key_pos_1 = definition.column_position_by_name("category").unwrap_or_default();
1417
1418                keys.extend(self.data()
1419                    .iter()
1420                    .map(|x| x[key_pos_0].data_to_string().to_string() + "_" + &x[key_pos_1].data_to_string())
1421                    .collect::<Vec<_>>()
1422                );
1423            }
1424
1425            // Uses custom formatting and : for concatenating keys: unit + ":" + faction + ":" + (general_unit ? "1" : "0").
1426            "units_custom_battle_permissions_tables" => {
1427                let key_pos_0 = definition.column_position_by_name("unit").unwrap_or_default();
1428                let key_pos_1 = definition.column_position_by_name("faction").unwrap_or_default();
1429                let key_pos_2 = definition.column_position_by_name("general_unit").unwrap_or_default();
1430
1431                keys.extend(self.data()
1432                    .iter()
1433                    .map(|x| {
1434                        let general_unit = if x[key_pos_2].data_to_string() == "true" { "1" } else { "0" };
1435                        x[key_pos_0].data_to_string().to_string() + ":" + &x[key_pos_1].data_to_string() + ":" + general_unit
1436                    })
1437                    .collect::<Vec<_>>()
1438                );
1439            }
1440
1441            // It includes the level_to_unlock string two times: category + level_to_unlock + level_to_unlock.
1442            "workshop_categories_progress_levels_tables" => {
1443                let key_pos_0 = definition.column_position_by_name("category").unwrap_or_default();
1444                let key_pos_1 = definition.column_position_by_name("level_to_unlock").unwrap_or_default();
1445
1446                keys.extend(self.data()
1447                    .iter()
1448                    .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())
1449                    .collect::<Vec<_>>()
1450                );
1451            }
1452
1453            // For anything else (single-keys and keys that follow the schemas without weird behavior), use the twad order.
1454            _ => {
1455                let key_cols = definition.key_column_positions_by_ca_order();
1456                keys.extend(self.data()
1457                    .iter()
1458                    .map(|x| key_cols.iter()
1459                        .map(|y| x[*y].data_to_string())
1460                        .join("")
1461                    )
1462                    .collect::<Vec<_>>()
1463                );
1464            }
1465        }
1466    }
1467}
1468
1469/// Implementation to create a `DB` from a `Table`.
1470impl From<TableInMemory> for DB {
1471    fn from(table: TableInMemory) -> Self {
1472        Self {
1473            mysterious_byte: true,
1474            guid: Uuid::new_v4().to_string(),
1475            table,
1476        }
1477    }
1478}