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