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