Skip to main content

rpfm_lib/schema/
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//! Schema system for defining Total War file formats.
12//!
13//! This module provides the infrastructure for defining and managing schemas that describe the binary
14//! structure of Total War game files, primarily database tables and localization files.
15//!
16//! # Overview
17//!
18//! A [`Schema`] contains [`Definition`]s that specify the exact binary layout of different file types.
19//! Each table can have multiple definitions to support different versions across game patches. The schema
20//! system also supports runtime patches to override field properties without modifying the base schema.
21//!
22//! # Key Components
23//!
24//! - [`Schema`]: The main container holding all table definitions and patches for a game
25//! - [`Definition`]: Describes one version of a table's structure (fields, types, constraints)
26//! - [`Field`]: Represents a single column in a table with its type and metadata
27//! - [`FieldType`]: The data type of a field (integers, strings, booleans, sequences, etc.)
28//! - [`DefinitionPatch`]: Runtime modifications to field properties
29//!
30//! # Schema Versioning
31//!
32//! - Each game has its own schema file (e.g., `warhammer_3.ron`)
33//! - The schema format version (currently v5) is tracked separately from table versions
34//! - Legacy schema formats (like v4) can be automatically upgraded via [`Schema::update()`]
35//!
36//! # Loading and Saving
37//!
38//! Schemas are typically stored in RON format but can also be exported to JSON:
39//!
40//! ```no_run
41//! use rpfm_lib::schema::Schema;
42//! use std::path::Path;
43//!
44//! // Load a schema
45//! let schema_path = Path::new("schemas/warhammer_3.ron");
46//! let schema = Schema::load(schema_path, None)?;
47//!
48//! // Access table definitions
49//! if let Some(defs) = schema.definitions_by_table_name("units_tables") {
50//!     for def in defs {
51//!         println!("Version {}: {} fields", def.version(), def.fields().len());
52//!     }
53//! }
54//! # Ok::<(), rpfm_lib::error::RLibError>(())
55//! ```
56//!
57//! # Patches
58//!
59//! Patches allow modifying field properties at runtime without changing the schema:
60//!
61//! ```no_run
62//! use rpfm_lib::schema::Schema;
63//! use std::path::Path;
64//!
65//! let schema = Schema::load(Path::new("schema.ron"), Some(Path::new("patches.ron")))?;
66//!
67//! // Check if a field has a patched value
68//! if let Some(value) = schema.patch_value("units_tables", "key", "is_key") {
69//!     println!("Patched is_key value: {}", value);
70//! }
71//! # Ok::<(), rpfm_lib::error::RLibError>(())
72//! ```
73//!
74//! # Schema Repository
75//!
76//! Schemas are maintained in a separate Git repository and can be updated independently from RPFM itself.
77//! The repository URL and branch are defined as constants in this module.
78
79use getset::*;
80#[cfg(feature = "integration_assembly_kit")] use itertools::Itertools;
81use rayon::prelude::*;
82use ron::de::{from_bytes, from_str};
83use ron::ser::{to_string_pretty, PrettyConfig};
84use serde::{Serialize as SerdeSerialize, Serializer};
85use serde_derive::{Serialize, Deserialize};
86
87use std::cmp::Ordering;
88use std::collections::{BTreeMap, HashMap};
89use std::{fmt, fmt::Display};
90use std::fs::{DirBuilder, File};
91use std::io::{BufReader, BufWriter, Read, Write};
92use std::path::Path;
93
94#[cfg(feature = "integration_assembly_kit")]use crate::integrations::assembly_kit::localisable_fields::RawLocalisableField;
95#[cfg(feature = "integration_assembly_kit")]use crate::integrations::assembly_kit::table_definition::RawDefinition;
96#[cfg(feature = "integration_assembly_kit")]use crate::integrations::assembly_kit::table_definition::RawField;
97#[cfg(feature = "integration_sqlite")] use rusqlite::types::Type;
98
99use crate::error::Result;
100use crate::files::table::DecodedData;
101use crate::games::supported_games::SupportedGames;
102
103// Legacy Schemas, to keep backwards compatibility during updates.
104pub(crate) mod v4;
105
106/// Name of the folder containing all the schemas.
107///
108/// This folder is located within the application's config directory and stores schema files
109/// for each supported Total War game.
110pub const SCHEMA_FOLDER: &str = "schemas";
111
112/// URL of the remote Git repository containing the schema files.
113///
114/// This repository is used to fetch and update schema definitions for all supported games.
115pub const SCHEMA_REPO: &str = "https://github.com/Frodo45127/rpfm-schemas";
116
117/// Name of the Git remote to use when fetching schemas.
118pub const SCHEMA_REMOTE: &str = "origin";
119
120/// Name of the Git branch to use when fetching schemas.
121pub const SCHEMA_BRANCH: &str = "master";
122
123/// Current structural version of the Schema, for compatibility purposes.
124///
125/// This version number is incremented when the schema format itself changes
126/// in a backwards-incompatible way.
127const CURRENT_STRUCTURAL_VERSION: u16 = 5;
128
129/// Invalid version marker for internal use.
130///
131/// This value is used for temporary or fake [`Definition`] instances that don't
132/// represent actual file versions.
133const INVALID_VERSION: i32 = -100;
134
135/// Name for unnamed colour groups.
136///
137/// When RGB colour fields are merged but have no common prefix, this name is used
138/// as the base name for the combined field.
139pub const MERGE_COLOUR_NO_NAME: &str = "Unnamed Colour Group";
140
141/// Suffix for merged colour field names.
142///
143/// This string is appended to the base name when creating a merged RGB colour field.
144/// For example, `banner_colour` fields would become `banner_colour_hex`.
145pub const MERGE_COLOUR_POST: &str = "_hex";
146
147/// Fields that can be ignored in missing field checks.
148///
149/// These fields are legacy fields from older Assembly Kit versions that are not
150/// actually used by the games and can be safely ignored during schema updates.
151#[cfg(feature = "integration_assembly_kit")]
152const IGNORABLE_FIELDS: [&str; 4] = ["s_ColLineage", "s_Generation", "s_GUID", "s_Lineage"];
153
154//---------------------------------------------------------------------------//
155//                              Enum & Structs
156//---------------------------------------------------------------------------//
157
158/// This type defines patches for specific table definitions, in a ColumnName -> [key -> value] format.
159///
160/// Patches allow runtime modification of schema fields without changing the base schema files.
161/// They are stored separately and applied when loading definitions.
162///
163/// # Structure
164///
165/// The outer [`HashMap`] maps column names to their patches. The inner [`HashMap`] maps patch keys
166/// to their values. For table-wide patches (not specific to a column), use the special column name `"-1"`.
167///
168/// # Example Patch Keys
169///
170/// - `"is_key"`: Override whether a field is a key field
171/// - `"default_value"`: Override the default value
172/// - `"is_filename"`: Override whether a field is a filename
173/// - `"filename_relative_path"`: Override the relative filename path
174/// - `"is_reference"`: Override reference information
175/// - `"lookup"`: Override lookup columns
176/// - `"lookup_hardcoded"`: Add hardcoded lookup values
177/// - `"description"`: Override the field description
178/// - `"not_empty"`: Mark the field as "cannot be empty"
179/// - `"unused"`: Mark a field as unused
180pub type DefinitionPatch = HashMap<String, HashMap<String, String>>;
181
182/// Represents a complete schema file containing table definitions for a Total War game.
183///
184/// A [`Schema`] stores the structural definitions for all database tables in a Total War game.
185/// Each table can have multiple [`Definition`] versions, allowing the schema to support
186/// different versions of the same table across game patches.
187///
188/// # Structure
189///
190/// - `version`: The structural version of the schema format itself (currently 5)
191/// - `definitions`: A map of table names to their version history
192/// - `patches`: Runtime modifications to field properties
193///
194/// # Usage
195///
196/// ```no_run
197/// use rpfm_lib::schema::Schema;
198/// use std::path::Path;
199///
200/// let schema_path = Path::new("schemas/warhammer_3.ron");
201/// let schema = Schema::load(schema_path, None)?;
202///
203/// // Get definitions for a specific table
204/// if let Some(definitions) = schema.definitions_by_table_name("units_tables") {
205///     println!("Found {} versions of units_tables", definitions.len());
206/// }
207/// # Ok::<(), rpfm_lib::error::RLibError>(())
208/// ```
209#[derive(Clone, PartialEq, Eq, Debug, Getters, MutGetters, Setters, Serialize, Deserialize)]
210#[getset(get = "pub", get_mut = "pub", set = "pub")]
211pub struct Schema {
212
213    /// The structural version of the schema format.
214    ///
215    /// This is incremented when the schema format itself changes in backwards-incompatible ways.
216    version: u16,
217
218    /// Map of table names to their version definitions.
219    ///
220    /// Each table can have multiple versions, stored as a [`Vec`] of [`Definition`] instances.
221    /// Serialization orders this map alphabetically for consistent output.
222    #[serde(serialize_with = "ordered_map_definitions")]
223    definitions: HashMap<String, Vec<Definition>>,
224
225    /// Map of table names to their patches.
226    ///
227    /// Patches allow runtime modification of field properties without changing the base schema.
228    /// See [`DefinitionPatch`] for the patch structure.
229    #[serde(serialize_with = "ordered_map_patches")]
230    patches: HashMap<String, DefinitionPatch>,
231}
232
233/// Defines the structure of a specific version of a database table.
234///
235/// A [`Definition`] specifies the exact binary layout and field properties for one version
236/// of a table. Tables can have multiple definitions in a schema to support different versions
237/// across game patches.
238///
239/// # Version Numbers
240///
241/// - `-1`: Fake definition used internally for dependency resolution
242/// - `0`: Unversioned files (tables without version markers in their binary format)
243/// - `1+`: Versioned files with explicit version numbers
244///
245/// # Fields Processing
246///
247/// The definition keeps a processed copy of the raw [`fields`] list, accessible via [`fields_processed()`]:
248/// - Bitwise fields are expanded into multiple boolean fields
249/// - Enum fields are converted to string fields
250/// - RGB colour triplets are merged into single ColourRGB fields
251/// - The definition's patches are applied to them
252///
253/// The copy is rebuilt every time the fields or the patches change, which is why both can only be changed through
254/// their setters. Unless you have a specific reason to do so, it is recommended to use [`fields_processed()`] instead of [`fields`].
255///
256/// # Serialization
257///
258/// Definitions serialize everything, processed fields and patches included, so they can be sent to clients without
259/// a schema. Schema files only store the raw parts, through [`StoredDefinition`]. When deserializing, the processed
260/// fields are always rebuilt from the raw ones.
261///
262/// # Localisation
263///
264/// Some tables have fields that are moved to separate LOC files during export:
265/// - [`localised_fields`] lists these fields
266/// - [`localised_key_order`] defines the key field order for LOC keys
267///
268/// [`fields_processed()`]: Definition::fields_processed
269/// [`fields`]: Definition::fields
270/// [`localised_fields`]: Definition::localised_fields
271/// [`localised_key_order`]: Definition::localised_key_order
272#[derive(Clone, PartialEq, Eq, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
273#[getset(get = "pub")]
274#[serde(from = "DefinitionData")]
275pub struct Definition {
276
277    /// The version number of this table definition.
278    ///
279    /// See type-level documentation for version number meanings.
280    #[getset(get_mut = "pub", set = "pub")]
281    version: i32,
282
283    /// List of fields in the order they appear in the binary format.
284    ///
285    /// This is the raw field list. For the processed version (with bitwise expansion,
286    /// enum conversion, etc.), use [`fields_processed()`].
287    ///
288    /// [`fields_processed()`]: Definition::fields_processed
289    fields: Vec<Field>,
290
291    /// Fields that are extracted to LOC files during export.
292    ///
293    /// These fields contain localisable text that gets separated from the main table data
294    /// when exporting said table to binary format.
295    #[getset(get_mut = "pub", set = "pub")]
296    localised_fields: Vec<Field>,
297
298    /// Order of key fields when constructing localisation keys.
299    ///
300    /// This specifies the order in which key fields should be concatenated when
301    /// creating LOC entry keys. Only applies to processed fields.
302    #[getset(get_mut = "pub", set = "pub")]
303    localised_key_order: Vec<u32>,
304
305    /// Runtime patches applied to this definition.
306    ///
307    /// These are loaded from the schema's patch set and applied when retrieving
308    /// the definition. Schema files don't store them here, but in the schema's patches field.
309    patches: DefinitionPatch,
310
311    /// The fields with processing and patches applied, in the order rows store their values.
312    ///
313    /// Rebuilt every time `fields` or `patches` change.
314    #[getset(skip)]
315    fields_processed: Vec<Field>,
316}
317
318/// Serialized form of a [`Definition`], used to rebuild its processed fields when deserializing it.
319#[derive(Deserialize)]
320struct DefinitionData {
321    version: i32,
322    fields: Vec<Field>,
323    localised_fields: Vec<Field>,
324    localised_key_order: Vec<u32>,
325
326    #[serde(default)]
327    patches: DefinitionPatch,
328
329    // Read but ignored, as it's rebuilt from the other fields. It still needs to be read
330    // so formats that read fields by position, like bitcode, don't misalign.
331    #[serde(default, rename = "fields_processed")]
332    _fields_processed: Vec<Field>,
333}
334
335/// View of a [`Definition`] with only what schema files store: no processed fields nor patches.
336///
337/// Get it with [`Definition::stored`].
338#[derive(Serialize)]
339pub struct StoredDefinition<'a> {
340    version: i32,
341    fields: Vec<StoredField<'a>>,
342    localised_fields: Vec<StoredField<'a>>,
343    localised_key_order: &'a [u32],
344}
345
346/// View of a [`Field`] with only what schema files store, used by [`StoredDefinition`].
347#[derive(Serialize)]
348struct StoredField<'a> {
349    name: &'a str,
350    #[serde(serialize_with = "serialize_stored_field_type")]
351    field_type: &'a FieldType,
352    is_key: bool,
353    default_value: &'a Option<String>,
354    is_filename: bool,
355    filename_relative_path: &'a Option<String>,
356    is_reference: &'a Option<(String, String)>,
357    lookup: &'a Option<Vec<String>>,
358    description: &'a str,
359    ca_order: i16,
360    is_bitwise: i32,
361    enum_values: &'a BTreeMap<i32, String>,
362    is_part_of_colour: Option<u8>,
363}
364
365/// Defines a single field within a table definition.
366///
367/// A [`Field`] describes one column in a database table, including its data type, constraints,
368/// and metadata. Fields can be modified at runtime via schema patches.
369///
370/// # Field Types
371///
372/// See [`FieldType`] for the supported data types (integers, strings, sequences, etc.).
373///
374/// # Field Attributes and Constraints
375///
376/// - **Key Fields**: When `is_key` is true, the field is part of the table's primary key
377/// - **References**: Fields can reference columns in other tables for foreign key relationships
378/// - **Lookups**: Additional columns from referenced tables to display in the UI
379/// - **Filenames**: Fields that contain file paths within the game's VFS
380/// - **Bitwise**: Numeric fields that should be split into multiple boolean columns
381/// - **Enums**: Numeric fields with named values
382/// - **Colours**: Fields that are part of an RGB triplet
383///
384/// # Patching
385///
386/// Most field properties can be overridden via schema patches. Use the accessor methods
387/// (e.g., [`is_key()`], [`default_value()`]) rather than direct field access to ensure
388/// patches are applied.
389///
390/// [`is_key()`]: Field::is_key
391/// [`default_value()`]: Field::default_value
392#[derive(Clone, PartialEq, Eq, Debug, Setters, Serialize, Deserialize)]
393#[getset(set = "pub")]
394pub struct Field {
395
396    /// Name of the field.
397    ///
398    /// Must match the field name from the Assembly Kit table definition (usually snake_case, but not always).
399    pub name: String,
400
401    /// Data type of the field.
402    ///
403    /// Determines how the field's binary data is interpreted.
404    pub field_type: FieldType,
405
406    /// Whether this field is part of the table's primary key.
407    ///
408    /// Can be overridden via patches. Use [`is_key()`] to get the patched value.
409    ///
410    /// [`is_key()`]: Field::is_key
411    pub is_key: bool,
412
413    /// Default value for this field when creating new rows.
414    ///
415    /// Can be overridden via patches. Use [`default_value()`] to get the patched value.
416    ///
417    /// [`default_value()`]: Field::default_value
418    pub default_value: Option<String>,
419
420    /// Whether this field contains a filename/path.
421    ///
422    /// Can be overridden via patches. Use [`is_filename()`] to get the patched value.
423    ///
424    /// [`is_filename()`]: Field::is_filename
425    pub is_filename: bool,
426
427    /// Semicolon-separated list of relative paths where files for this field can be found.
428    ///
429    /// Only applicable when `is_filename` is true. Can be overridden via patches.
430    /// Use [`filename_relative_path()`] to get the parsed, patched value.
431    ///
432    /// [`filename_relative_path()`]: Field::filename_relative_path
433    pub filename_relative_path: Option<String>,
434
435    /// Foreign key reference to another table.
436    ///
437    /// Format: `Some((table_name, column_name))` where `table_name` doesn't include
438    /// the `_tables` suffix. Can be overridden via patches.
439    /// Use [`is_reference()`] to get the patched value.
440    ///
441    /// [`is_reference()`]: Field::is_reference
442    pub is_reference: Option<(String, String)>,
443
444    /// Additional columns from the referenced table to show in lookups.
445    ///
446    /// Only applicable when `is_reference` is Some. Can be overridden via patches.
447    /// Use [`lookup()`] to get the patched value.
448    ///
449    /// [`lookup()`]: Field::lookup
450    pub lookup: Option<Vec<String>>,
451
452    /// Human-readable description of the field's purpose.
453    ///
454    /// Can be overridden via patches. Use [`description()`] to get the patched value.
455    ///
456    /// [`description()`]: Field::description
457    pub description: String,
458
459    /// Visual position in CA's Assembly Kit table editor.
460    ///
461    /// `-1` means the position is unknown. This is used to maintain column order
462    /// consistency with the Assembly Kit.
463    pub ca_order: i16,
464
465    /// Number of boolean columns this field should be split into.
466    ///
467    /// Only applicable to numeric fields. A value > 1 means the field should be
468    /// expanded into that many boolean columns when processed.
469    pub is_bitwise: i32,
470
471    /// Named values for this field when treated as an enum.
472    ///
473    /// Maps integer values to their string names. When non-empty, the field
474    /// is treated as a string enum in processed fields.
475    ///
476    /// NOTE: When possible, prefer using lookups instead of enum_values.
477    pub enum_values: BTreeMap<i32, String>,
478
479    /// Index of the RGB colour group this field belongs to.
480    ///
481    /// When set, this field is part of a 3-field RGB triplet that should be
482    /// merged into a single ColourRGB field when processed.
483    pub is_part_of_colour: Option<u8>,
484
485    /// Whether this field is unused by the game.
486    ///
487    /// Determined via patches at runtime, so schema files don't store it.
488    /// Use [`unused()`] to get the patched value.
489    ///
490    /// [`unused()`]: Field::unused
491    #[serde(default)]
492    pub unused: bool,
493}
494
495/// Supported data types for table fields.
496///
497/// This enum defines all field types that can be encoded/decoded from Total War database tables.
498/// Each variant corresponds to a specific binary representation in the game files.
499///
500/// # Basic Types
501///
502/// - **Boolean**: 1-byte boolean value
503/// - **Integers**: Signed integers of various sizes (I16, I32, I64)
504/// - **Floats**: Floating-point numbers (F32, F64)
505/// - **Strings**: Length-prefixed strings with [`u8`] or [`u16`] length markers
506///
507/// # Optional Types
508///
509/// Optional types use a 1-byte flag followed by the value if present:
510/// - **OptionalI16**, **OptionalI32**, **OptionalI64**: Optional integers
511/// - **OptionalStringU8**, **OptionalStringU16**: Optional strings
512///
513/// # Complex Types
514///
515/// - **ColourRGB**: 6-character hexadecimal RGB colour (e.g., "FF0000" for red)
516/// - **SequenceU16**, **SequenceU32**: Arrays with [`u16`] or [`u32`] length prefix
517///
518/// # Sequences
519///
520/// Sequence types contain a nested [`Definition`] that describes the structure of each
521/// array element. The length prefix determines how many elements follow.
522#[derive(Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
523pub enum FieldType {
524    /// 1-byte boolean value (0 = false, 1 = true).
525    Boolean,
526
527    /// 32-bit floating-point number.
528    F32,
529
530    /// 64-bit floating-point number.
531    F64,
532
533    /// 16-bit signed integer.
534    I16,
535
536    /// 32-bit signed integer.
537    I32,
538
539    /// 64-bit signed integer.
540    I64,
541
542    /// RGB colour as a 6-character hexadecimal string (e.g., "FF0000").
543    ColourRGB,
544
545    /// UTF-8 encoded string with [`u16`] length prefix (max 65535 bytes).
546    #[default]
547    StringU8,
548
549    /// UTF-16 encoded string with [`u16`] length prefix (max 65535 characters).
550    StringU16,
551
552    /// Optional 16-bit signed integer (1-byte flag + value if present).
553    OptionalI16,
554
555    /// Optional 32-bit signed integer (1-byte flag + value if present).
556    OptionalI32,
557
558    /// Optional 64-bit signed integer (1-byte flag + value if present).
559    OptionalI64,
560
561    /// Optional UTF-8 encoded string (1-byte flag + [`u16`] length prefix + string if present).
562    OptionalStringU8,
563
564    /// Optional UTF-16 encoded string (1-byte flag + [`u16`] length prefix + string if present).
565    OptionalStringU16,
566
567    /// Array with [`u16`] element count followed by elements matching the nested definition.
568    SequenceU16(Box<Definition>),
569
570    /// Array with [`u32`] element count followed by elements matching the nested definition.
571    SequenceU32(Box<Definition>)
572}
573
574//---------------------------------------------------------------------------//
575//                       Enum & Structs Implementations
576//---------------------------------------------------------------------------//
577
578/// Implementation of [`Schema`].
579impl Schema {
580
581    /// Saves patches to a local patches file, merging with existing patches.
582    ///
583    /// This function loads existing patches from the file, merges the provided patches with them,
584    /// and writes the combined patch set back to the file in RON format.
585    ///
586    /// # Arguments
587    ///
588    /// * `patches` - The patches to add or update
589    /// * `path` - Path to the local patches file (must exist)
590    ///
591    /// # Returns
592    ///
593    /// Returns [`Ok`] if successful, or an error if:
594    /// - The file cannot be read or written
595    /// - The file contains invalid patch data
596    ///
597    /// # Example
598    ///
599    /// ```no_run
600    /// use std::collections::HashMap;
601    /// use std::path::Path;
602    /// use rpfm_lib::schema::{Schema, DefinitionPatch};
603    ///
604    /// let mut patches: HashMap<String, DefinitionPatch> = HashMap::new();
605    /// // Add patches...
606    ///
607    /// Schema::save_patches(&patches, Path::new("my_patches.ron"))?;
608    /// # Ok::<(), rpfm_lib::error::RLibError>(())
609    /// ```
610    pub fn save_patches(patches: &HashMap<String, DefinitionPatch>, path: &Path) -> Result<()> {
611        let mut file = BufReader::new(File::open(path)?);
612        let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
613        file.read_to_end(&mut data)?;
614        let mut local_patches: HashMap<String, DefinitionPatch> = from_bytes(&data)?;
615
616        Self::add_patches_to_patch_set(&mut local_patches, patches);
617
618        let mut file = BufWriter::new(File::create(path)?);
619        let config = PrettyConfig::default();
620        file.write_all(to_string_pretty(&local_patches, config)?.as_bytes())?;
621
622        Ok(())
623    }
624
625    /// Removes all local patches for a specific table.
626    ///
627    /// This function loads the patches file, removes all patches for the specified table,
628    /// and writes the updated patch set back to the file.
629    ///
630    /// # Arguments
631    ///
632    /// * `table_name` - Name of the table to remove patches for
633    /// * `path` - Path to the local patches file
634    ///
635    /// # Returns
636    ///
637    /// Returns [`Ok`] if successful, even if no there were no patches to remove, or an error
638    /// if file I/O fails.
639    pub fn remove_patches_for_table(table_name: &str, path: &Path) -> Result<()> {
640        let mut file = BufReader::new(File::open(path)?);
641        let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
642        file.read_to_end(&mut data)?;
643        let mut local_patches: HashMap<String, DefinitionPatch> = from_bytes(&data)?;
644
645        local_patches.remove(table_name);
646
647        let mut file = BufWriter::new(File::create(path)?);
648        let config = PrettyConfig::default();
649        file.write_all(to_string_pretty(&local_patches, config)?.as_bytes())?;
650
651        Ok(())
652    }
653
654    /// Removes all local patches for a specific field in a table.
655    ///
656    /// This function loads the patches file, removes all patches for the specified table's field,
657    /// and writes the updated patch set back to the file. Other fields in the table are unaffected.
658    ///
659    /// # Arguments
660    ///
661    /// * `table_name` - Name of the table containing the field
662    /// * `field_name` - Name of the field to remove patches for
663    /// * `path` - Path to the local patches file
664    ///
665    /// # Returns
666    ///
667    /// Returns [`Ok`] if successful, even if no there were no patches to remove, or an error
668    /// if file I/O fails.
669    pub fn remove_patches_for_table_and_field(table_name: &str, field_name: &str, path: &Path) -> Result<()> {
670        let mut file = BufReader::new(File::open(path)?);
671        let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
672        file.read_to_end(&mut data)?;
673        let mut local_patches: HashMap<String, DefinitionPatch> = from_bytes(&data)?;
674
675        if let Some(table_patches) = local_patches.get_mut(table_name) {
676            table_patches.remove(field_name);
677            if table_patches.is_empty() {
678                local_patches.remove(table_name);
679            }
680        }
681
682        let mut file = BufWriter::new(File::create(path)?);
683        let config = PrettyConfig::default();
684        file.write_all(to_string_pretty(&local_patches, config)?.as_bytes())?;
685
686        Ok(())
687    }
688
689    /// Retrieves a specific patch value for a table's column.
690    ///
691    /// # Arguments
692    ///
693    /// * `table_name` - Name of the table
694    /// * `column_name` - Name of the column
695    /// * `key` - Patch key (e.g., "is_key", "default_value")
696    ///
697    /// # Returns
698    ///
699    /// Returns the patch value if found, or [`None`] otherwise.
700    pub fn patch_value(&self, table_name: &str, column_name: &str, key: &str) -> Option<&String> {
701        self.patches.get(table_name)?.get(column_name)?.get(key)
702    }
703
704    /// Retrieves all patches for a specific table.
705    ///
706    /// # Arguments
707    ///
708    /// * `table_name` - Name of the table
709    ///
710    /// # Returns
711    ///
712    /// Returns the table's patches if found, or [`None`] otherwise.
713    pub fn patches_for_table(&self, table_name: &str) -> Option<&DefinitionPatch> {
714        self.patches.get(table_name)
715    }
716
717    /// Merges patches into an existing patch set.
718    ///
719    /// This function adds the provided patches to the patch set, merging them with any
720    /// existing patches. If a patch already exists for a table/column/key combination,
721    /// it will be extended with the new values.
722    ///
723    /// # Arguments
724    ///
725    /// * `patch_set` - The patch set to merge into (modified in place)
726    /// * `patches` - The patches to add
727    ///
728    /// # Note
729    ///
730    /// After adding patches, you must re-retrieve any definitions you've already retrieved
731    /// for the patches to take effect, as patches are applied when retrieving definitions.
732    pub fn add_patches_to_patch_set(patch_set: &mut HashMap<String, DefinitionPatch>, patches: &HashMap<String, DefinitionPatch>) {
733        patches.iter().for_each(|(table_name, column_patch)| {
734            match patch_set.get_mut(table_name) {
735                Some(column_patch_current) => {
736                    column_patch.iter().for_each(|(column_name, patch)| {
737                        match column_patch_current.get_mut(column_name) {
738                            Some(patch_current) => patch_current.extend(patch.clone()),
739                            None => {
740                                column_patch_current.insert(column_name.to_owned(), patch.clone());
741                            }
742                        }
743                    });
744                }
745                None => {
746                    patch_set.insert(table_name.to_owned(), column_patch.clone());
747                }
748            }
749        });
750    }
751
752    /// Adds or updates a table definition in the schema.
753    ///
754    /// If a definition with the same version already exists for this table, it will be replaced.
755    /// Otherwise, the definition is added to the table's version list.
756    ///
757    /// # Arguments
758    ///
759    /// * `table_name` - Name of the table
760    /// * `definition` - The definition to add or update
761    pub fn add_definition(&mut self, table_name: &str, definition: &Definition) {
762        match self.definitions.get_mut(table_name) {
763            Some(definitions) => {
764                match definitions.iter_mut().find(|def| def.version() == definition.version()) {
765                    Some(def) => *def = definition.to_owned(),
766                    None => definitions.push(definition.to_owned()),
767                }
768            },
769            None => { self.definitions.insert(table_name.to_owned(), vec![definition.to_owned()]); },
770        }
771    }
772
773    /// Removes a specific table definition version from the schema.
774    ///
775    /// # Arguments
776    ///
777    /// * `table_name` - Name of the table
778    /// * `version` - Version number of the definition to remove
779    pub fn remove_definition(&mut self, table_name: &str, version: i32) {
780        if let Some(definitions) = self.definitions.get_mut(table_name) {
781            let mut index_to_delete = vec![];
782            for (index, definition) in definitions.iter().enumerate() {
783                if definition.version == version {
784                    index_to_delete.push(index);
785                }
786            }
787
788            index_to_delete.iter().rev().for_each(|index| { definitions.remove(*index); });
789        }
790    }
791
792    /// Returns a cloned copy of all definitions for a table.
793    ///
794    /// # Arguments
795    ///
796    /// * `table_name` - Name of the table
797    ///
798    /// # Returns
799    ///
800    /// Returns a cloned vector of all definitions for the table, or [`None`] if not found.
801    pub fn definitions_by_table_name_cloned(&self, table_name: &str) -> Option<Vec<Definition>> {
802        self.definitions.get(table_name).cloned()
803    }
804
805    /// Returns a reference to all definitions for a table.
806    ///
807    /// # Arguments
808    ///
809    /// * `table_name` - Name of the table
810    ///
811    /// # Returns
812    ///
813    /// Returns a reference to the vector of definitions, or [`None`] if not found.
814    pub fn definitions_by_table_name(&self, table_name: &str) -> Option<&Vec<Definition>>  {
815        self.definitions.get(table_name)
816    }
817
818    /// Returns a mutable reference to all definitions for a table.
819    ///
820    /// # Arguments
821    ///
822    /// * `table_name` - Name of the table
823    ///
824    /// # Returns
825    ///
826    /// Returns a mutable reference to the vector of definitions, or [`None`] if not found.
827    pub fn definitions_by_table_name_mut(&mut self, table_name: &str) -> Option<&mut Vec<Definition>>  {
828        self.definitions.get_mut(table_name)
829    }
830
831    /// Returns the newest compatible definition for a table based on candidate versions.
832    ///
833    /// This function first tries to find a definition matching the highest version number
834    /// from the candidates (typically from a dependency database). If that fails, it
835    /// falls back to the first (newest) definition in the schema.
836    ///
837    /// # Arguments
838    ///
839    /// * `table_name` - Name of the table
840    /// * `candidates` - List of candidate definitions (typically from dependencies)
841    ///
842    /// # Returns
843    ///
844    /// Returns the best matching definition, or [`None`] if the table is not found.
845    pub fn definition_newer(&self, table_name: &str, candidates: &[Definition]) -> Option<&Definition> {
846
847        // Version is... complicated. We don't really want the last one, but the last one compatible with our game.
848        // So we have to try to get it first from the Dependency Database first. If that fails, we fall back to the schema.
849        if let Some(definition) = candidates.iter().max_by(|x, y| x.version().cmp(y.version())) {
850            self.definition_by_name_and_version(table_name, *definition.version())
851        }
852
853        // If there was no coincidence in the dependency database... we risk ourselves getting the last definition we have for
854        // that db from the schema.
855        else{
856            self.definitions.get(table_name)?.first()
857        }
858    }
859
860    /// Returns a reference to a specific table definition by name and version.
861    ///
862    /// # Arguments
863    ///
864    /// * `table_name` - Name of the table
865    /// * `table_version` - Version number of the definition
866    ///
867    /// # Returns
868    ///
869    /// Returns the definition if found, or [`None`] otherwise.
870    pub fn definition_by_name_and_version(&self, table_name: &str, table_version: i32) -> Option<&Definition>  {
871        self.definitions.get(table_name)?.iter().find(|definition| *definition.version() == table_version)
872    }
873
874    /// Returns the definition of a table that keeps the most of the provided field names.
875    ///
876    /// Ties go to the first (newest) definition.
877    pub fn definition_by_name_and_fields(&self, table_name: &str, field_names: &[&str]) -> Option<&Definition> {
878        self.definitions.get(table_name)?.iter()
879            .map(|definition| {
880                let fields = definition.fields_processed();
881                let kept = field_names.iter().filter(|name| fields.iter().any(|field| field.name() == **name)).count();
882                (definition, kept)
883            })
884            .fold(None, |best: Option<(&Definition, usize)>, candidate| match best {
885                Some((_, best_kept)) if best_kept >= candidate.1 => best,
886                _ => Some(candidate),
887            })
888            .map(|(definition, _)| definition)
889    }
890
891    /// Returns a mutable reference to a specific table definition by name and version.
892    ///
893    /// # Arguments
894    ///
895    /// * `table_name` - Name of the table
896    /// * `table_version` - Version number of the definition
897    ///
898    /// # Returns
899    ///
900    /// Returns the definition if found, or [`None`] otherwise.
901    pub fn definition_by_name_and_version_mut(&mut self, table_name: &str, table_version: i32) -> Option<&mut Definition>  {
902        self.definitions.get_mut(table_name)?.iter_mut().find(|definition| *definition.version() == table_version)
903    }
904
905    /// Loads a [`Schema`] from a RON file, optionally merging local patches.
906    ///
907    /// This function loads a schema from a `.ron` file and applies any patches from both
908    /// the schema itself and an optional local patches file. Patches from the local file
909    /// are merged with schema patches and applied to all definitions.
910    ///
911    /// # Arguments
912    ///
913    /// * `path` - Path to the schema `.ron` file
914    /// * `local_patches` - Optional path to a local patches file
915    ///
916    /// # Returns
917    ///
918    /// Returns the loaded schema with all patches applied, or an error if loading fails.
919    pub fn load(path: &Path, local_patches: Option<&Path>) -> Result<Self> {
920        let mut file = BufReader::new(File::open(path)?);
921        let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
922        file.read_to_end(&mut data)?;
923        let mut schema: Self = from_bytes(&data)?;
924        let mut patches = schema.patches().clone();
925
926        // If we got local patches, add them to the patches list.
927        //
928        // NOTE: we separate the patches from the schemas because otherwise an schema edit will save local patches into the schema,
929        // and we want them to remain local.
930        if let Some(path) = local_patches {
931            if let Ok(file) = File::open(path) {
932                let mut file = BufReader::new(file);
933                let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
934                file.read_to_end(&mut data)?;
935                if let Ok(local_patches) = from_bytes::<HashMap<String, DefinitionPatch>>(&data) {
936                    Self::add_patches_to_patch_set(&mut patches, &local_patches);
937                }
938            }
939        }
940
941        // Preload all patches to their respective definitions.
942        for (table_name, patches) in &patches {
943            if let Some(definitions) = schema.definitions_by_table_name_mut(table_name) {
944                for definition in definitions {
945                    definition.set_patches(patches.clone());
946                }
947            }
948        }
949
950        Ok(schema)
951    }
952
953    /// Loads a [`Schema`] from a JSON file.
954    ///
955    /// Similar to [`load()`], but reads from a JSON file instead of RON. Applies all
956    /// patches from the schema to the definitions.
957    ///
958    /// # Arguments
959    ///
960    /// * `path` - Path to the schema `.json` file
961    ///
962    /// # Returns
963    ///
964    /// Returns the loaded schema with patches applied, or an error if loading fails.
965    ///
966    /// [`load()`]: Schema::load
967    pub fn load_json(path: &Path) -> Result<Self> {
968        let mut file = BufReader::new(File::open(path)?);
969        let mut data = Vec::with_capacity(file.get_ref().metadata()?.len() as usize);
970        file.read_to_end(&mut data)?;
971        let mut schema: Self = serde_json::from_slice(&data)?;
972
973        // Preload all patches to their respective definitions.
974        for (table_name, patches) in schema.patches().clone() {
975            if let Some(definitions) = schema.definitions_by_table_name_mut(&table_name) {
976                for definition in definitions {
977                    definition.set_patches(patches.clone());
978                }
979            }
980        }
981
982        Ok(schema)
983    }
984
985    /// Saves the schema to a RON file.
986    ///
987    /// This function saves the schema to a `.ron` file, automatically:
988    /// - Creating parent directories if needed
989    /// - Sorting definitions by version (newest first)
990    /// - Cleaning up invalid references
991    /// - Moving certain patches from definitions to schema patches
992    ///
993    /// # Arguments
994    ///
995    /// * `path` - Path where the schema file should be saved
996    ///
997    /// # Returns
998    ///
999    /// Returns [`Ok`] if saved successfully, or an error if file I/O fails.
1000    pub fn save(&mut self, path: &Path) -> Result<()> {
1001
1002        // Make sure the path exists to avoid problems with updating schemas.
1003        if let Some(parent_folder) = path.parent() {
1004            DirBuilder::new().recursive(true).create(parent_folder)?;
1005        }
1006
1007        let mut file = BufWriter::new(File::create(path)?);
1008        let config = PrettyConfig::default();
1009
1010        let mut patches = HashMap::new();
1011
1012        // Make sure all definitions are properly sorted by version number.
1013        self.definitions.iter_mut().for_each(|(table_name, definitions)| {
1014            definitions.sort_by(|a, b| b.version().cmp(a.version()));
1015
1016            // Fix for empty dependencies, again.
1017            definitions.iter_mut().for_each(|definition| {
1018                let mut references_fixed = false;
1019                definition.fields.iter_mut().for_each(|field| {
1020                    if let Some((ref_table, ref_column)) = field.is_reference(None) {
1021                        if ref_table.trim().is_empty() || ref_column.trim().is_empty() {
1022                            field.is_reference = None;
1023                            references_fixed = true;
1024                        }
1025                    }
1026                });
1027
1028                if references_fixed {
1029                    definition.update_fields_processed();
1030                }
1031
1032                // Move any lookup_hardcoded and unused patches to schema patches, merging them with the ones from other versions.
1033                let mut def_patches = definition.patches().clone();
1034                def_patches.retain(|_, value| {
1035                    value.retain(|key, _| key == "lookup_hardcoded" || key == "unused");
1036                    !value.is_empty()
1037                });
1038
1039                if !def_patches.is_empty() {
1040                    let table_patches: &mut DefinitionPatch = patches.entry(table_name.to_owned()).or_default();
1041                    for (column, column_patch) in def_patches {
1042                        table_patches.entry(column).or_default().extend(column_patch);
1043                    }
1044                }
1045            })
1046        });
1047
1048        Self::add_patches_to_patch_set(self.patches_mut(), &patches);
1049
1050        file.write_all(to_string_pretty(&self, config)?.as_bytes())?;
1051        Ok(())
1052    }
1053
1054    /// Saves the schema to a JSON file.
1055    ///
1056    /// This function saves the schema to a `.json` file at the specified path, automatically:
1057    /// - Creating parent directories if needed
1058    /// - Changing the extension to `.json`
1059    /// - Sorting definitions by version (newest first)
1060    /// - Pretty-printing the JSON output
1061    ///
1062    /// # Arguments
1063    ///
1064    /// * `path` - Path where the schema file should be saved (extension will be changed to `.json`)
1065    ///
1066    /// # Returns
1067    ///
1068    /// Returns [`Ok`] if saved successfully, or an error if file I/O or serialization fails.
1069    pub fn save_json(&mut self, path: &Path) -> Result<()> {
1070        let mut path = path.to_path_buf();
1071        path.set_extension("json");
1072
1073        // Make sure the path exists to avoid problems with updating schemas.
1074        if let Some(parent_folder) = path.parent() {
1075            DirBuilder::new().recursive(true).create(parent_folder)?;
1076        }
1077
1078        let mut file = BufWriter::new(File::create(&path)?);
1079
1080        // Make sure all definitions are properly sorted by version number.
1081        self.definitions.iter_mut().for_each(|(_, definitions)| {
1082            definitions.sort_by(|a, b| b.version().cmp(a.version()));
1083        });
1084
1085        file.write_all(serde_json::to_string_pretty(&self)?.as_bytes())?;
1086        Ok(())
1087    }
1088
1089    /// Exports all schema files in a folder to JSON format.
1090    ///
1091    /// This function loads all schema files (`.ron`) for supported games from the specified folder
1092    /// and saves them as `.json` files in the same location. This is primarily used for
1093    /// compatibility with external tools that prefer JSON.
1094    ///
1095    /// # Arguments
1096    ///
1097    /// * `schema_folder_path` - Path to the folder containing schema `.ron` files
1098    ///
1099    /// # Returns
1100    ///
1101    /// Returns [`Ok`] if all schemas are successfully exported, or an error if any operation fails.
1102    ///
1103    /// # Note
1104    ///
1105    /// This function processes schemas in parallel for better performance.
1106    pub fn export_to_json(schema_folder_path: &Path) -> Result<()> {
1107        let games = SupportedGames::default();
1108
1109        games.games_sorted().par_iter().map(|x| x.schema_file_name()).try_for_each(|schema_file| {
1110            let mut schema_path = schema_folder_path.to_owned();
1111            schema_path.push(schema_file);
1112
1113            let mut schema = Schema::load(&schema_path, None)?;
1114            schema_path.set_extension("json");
1115            schema.save_json(&schema_path)?;
1116            Ok(())
1117        })
1118    }
1119
1120    /// Updates a schema from a legacy format to the current format.
1121    ///
1122    /// This function handles migration of schema files from older structural versions (e.g., v4)
1123    /// to the current structural version (v5). It automatically detects the schema version and
1124    /// applies the necessary transformations.
1125    ///
1126    /// # Arguments
1127    ///
1128    /// * `schema_path` - Path to the schema file to update
1129    /// * `schema_patches_path` - Path to the schema patches file
1130    /// * `game_name` - Name of the game this schema is for
1131    ///
1132    /// # Returns
1133    ///
1134    /// Returns [`Ok`] if the update succeeds, or an error if the update process fails.
1135    pub fn update(schema_path: &Path, schema_patches_path: &Path, game_name: &str) -> Result<()>{
1136        v4::SchemaV4::update(schema_path, schema_patches_path, game_name)
1137    }
1138
1139    /// Returns all columns that reference fields in the specified table.
1140    ///
1141    /// This function searches through all table definitions in the schema to find fields
1142    /// that have foreign key references pointing to the provided table's fields.
1143    ///
1144    /// # Arguments
1145    ///
1146    /// * `table_name` - Name of the table to find references to
1147    /// * `definition` - Definition of the table (used to get the field list)
1148    ///
1149    /// # Returns
1150    ///
1151    /// Returns a map where:
1152    /// - Keys are local field names from the provided definition
1153    /// - Values are maps of `table_name -> Vec<field_name>` containing all referencing fields
1154    ///
1155    /// # Example
1156    ///
1157    /// For a `factions_tables` table, this might return:
1158    /// ```text
1159    /// {
1160    ///   "key": {
1161    ///     "units_tables": ["faction_key"],
1162    ///     "characters_tables": ["faction_key", "home_faction_key"]
1163    ///   }
1164    /// }
1165    /// ```
1166    pub fn referencing_columns_for_table(&self, table_name: &str, definition: &Definition) -> HashMap<String, HashMap<String, Vec<String>>> {
1167
1168        // Iterate over all definitions and find the ones referencing our table/field.
1169        let fields_processed = definition.fields_processed();
1170        let definitions = self.definitions();
1171        let table_name_no_tables = table_name.to_owned().drain(..table_name.len() - 7).collect::<String>();
1172
1173        fields_processed.iter().filter_map(|field| {
1174
1175            let references = definitions.par_iter().filter_map(|(ver_name, ver_definitions)| {
1176                let mut references = ver_definitions.iter().filter_map(|ver_definition| {
1177                    let ver_patches = Some(ver_definition.patches());
1178                    let references = ver_definition.fields_processed().iter().filter_map(|ver_field| {
1179                        if let Some((source_table_name, source_column_name)) = ver_field.is_reference(ver_patches) {
1180                            if table_name_no_tables == source_table_name && field.name() == source_column_name {
1181                                Some(ver_field.name().to_owned())
1182                            } else { None }
1183                        } else { None }
1184                    }).collect::<Vec<String>>();
1185                    if references.is_empty() {
1186                        None
1187                    } else {
1188                        Some(references)
1189                    }
1190                }).flatten().collect::<Vec<String>>();
1191                if references.is_empty() {
1192                    None
1193                } else {
1194                    references.sort();
1195                    references.dedup();
1196                    Some((ver_name.to_owned(), references))
1197                }
1198            }).collect::<HashMap<String, Vec<String>>>();
1199            if references.is_empty() {
1200                None
1201            } else {
1202                Some((field.name().to_owned(), references))
1203            }
1204        }).collect()
1205    }
1206
1207    /// Returns all tables and columns that reference the specified column, and whether LOC files may be affected.
1208    ///
1209    /// This function performs a recursive search to find all fields that reference the specified column,
1210    /// including indirect references (fields that reference fields that reference the target column).
1211    /// It also checks if changing the column would affect localisation keys.
1212    ///
1213    /// # Arguments
1214    ///
1215    /// * `table_name` - Name of the table containing the column (with or without `_tables` suffix)
1216    /// * `column_name` - Name of the column to find references to
1217    /// * `fields` - The table's field list
1218    /// * `localised_fields` - The table's localised field list
1219    ///
1220    /// # Returns
1221    ///
1222    /// Returns a tuple of:
1223    /// - A map of `table_name -> Vec<field_name>` containing all referencing fields (recursively)
1224    /// - A boolean indicating if LOC files may need updates (true if the column is a key field and the table has localised fields)
1225    ///
1226    /// # Note
1227    ///
1228    /// Recursion is supported for table references, but not for LOC field detection.
1229    pub fn tables_and_columns_referencing_our_own(
1230        &self,
1231        table_name: &str,
1232        column_name: &str,
1233        fields: &[Field],
1234        localised_fields: &[Field]
1235    ) -> (BTreeMap<String, Vec<String>>, bool) {
1236
1237        // Make sure the table name is correct.
1238        let short_table_name = if table_name.ends_with("_tables") { table_name.split_at(table_name.len() - 7).0 } else { table_name };
1239        let mut tables: BTreeMap<String, Vec<String>> = BTreeMap::new();
1240
1241        // We get all the db definitions from the schema, then iterate all of them to find what tables/columns reference our own.
1242        for (ref_table_name, ref_definition) in self.definitions() {
1243            let mut columns: Vec<String> = vec![];
1244            for ref_version in ref_definition {
1245                let ref_fields = ref_version.fields_processed();
1246                let ref_patches = Some(ref_version.patches());
1247                let ref_fields_localised = ref_version.localised_fields();
1248                for ref_field in ref_fields {
1249                    if let Some((ref_ref_table, ref_ref_field)) = ref_field.is_reference(ref_patches) {
1250
1251                        // As this applies to all versions of a table, skip repeated fields.
1252                        if ref_ref_table == short_table_name && ref_ref_field == column_name && !columns.iter().any(|x| x == ref_field.name()) {
1253                            columns.push(ref_field.name().to_owned());
1254
1255                            // If we find a referencing column, get recursion working to check if there is any column referencing this one that needs to be edited.
1256                            let (ref_of_ref, _) = self.tables_and_columns_referencing_our_own(ref_table_name, ref_field.name(), ref_fields, ref_fields_localised);
1257                            for refs in &ref_of_ref {
1258                                match tables.get_mut(refs.0) {
1259                                    Some(columns) => for value in refs.1 {
1260                                        if !columns.contains(value) {
1261                                            columns.push(value.to_owned());
1262                                        }
1263                                    }
1264                                    None => { tables.insert(refs.0.to_owned(), refs.1.to_vec()); },
1265                                }
1266                            }
1267                        }
1268                    }
1269                }
1270            }
1271
1272            // Only add them if we actually found columns.
1273            if !columns.is_empty() {
1274                tables.insert(ref_table_name.to_owned(), columns);
1275            }
1276        }
1277
1278        // Also, check if we have to be careful about localised fields.
1279        let patches = self.patches().get(table_name);
1280        let has_loc_fields = if let Some(field) = fields.iter().find(|x| x.name() == column_name) {
1281            (field.is_key(patches) || field.name() == "key") && !localised_fields.is_empty()
1282        } else { false };
1283
1284        (tables, has_loc_fields)
1285    }
1286    /// Loads patches from a RON-formatted string.
1287    ///
1288    /// # Arguments
1289    ///
1290    /// * `patch` - RON-formatted string containing patches
1291    ///
1292    /// # Returns
1293    ///
1294    /// Returns the parsed patches, or an error if the string is not valid RON.
1295    pub fn load_patches_from_str(patch: &str) -> Result<HashMap<String, DefinitionPatch>> {
1296        from_str(patch).map_err(From::from)
1297    }
1298
1299    /// Loads definitions from a RON-formatted string.
1300    ///
1301    /// # Arguments
1302    ///
1303    /// * `definition` - RON-formatted string containing table definitions
1304    ///
1305    /// # Returns
1306    ///
1307    /// Returns the parsed definitions, or an error if the string is not valid RON.
1308    pub fn load_definitions_from_str(definition: &str) -> Result<HashMap<String, Definition>> {
1309        from_str(definition).map_err(From::from)
1310    }
1311
1312    /// Exports patches to a RON-formatted string.
1313    ///
1314    /// # Arguments
1315    ///
1316    /// * `patches` - The patches to export
1317    ///
1318    /// # Returns
1319    ///
1320    /// Returns the RON-formatted string, or an error if serialization fails.
1321    pub fn export_patches_to_str(patches: &HashMap<String, DefinitionPatch>) -> Result<String> {
1322        let config = PrettyConfig::default();
1323        ron::ser::to_string_pretty(&patches, config).map_err(From::from)
1324    }
1325
1326    /// Exports definitions to a RON-formatted string.
1327    ///
1328    /// # Arguments
1329    ///
1330    /// * `definitions` - The definitions to export
1331    ///
1332    /// # Returns
1333    ///
1334    /// Returns the RON-formatted string, or an error if serialization fails.
1335    pub fn export_definitions_to_str(definitions: &HashMap<String, Definition>) -> Result<String> {
1336        let config = PrettyConfig::default();
1337        let stored = definitions.iter().map(|(table_name, definition)| (table_name, definition.stored())).collect::<HashMap<_, _>>();
1338        ron::ser::to_string_pretty(&stored, config).map_err(From::from)
1339    }
1340}
1341
1342/// Implementation of [`Definition`].
1343impl Definition {
1344
1345    /// Creates a new empty definition for a specific version.
1346    ///
1347    /// # Arguments
1348    ///
1349    /// * `version` - The version number for this definition
1350    /// * `schema_patches` - Optional patches to apply to this definition
1351    ///
1352    /// # Returns
1353    ///
1354    /// Returns a new empty definition with no fields.
1355    pub fn new(version: i32, schema_patches: Option<&DefinitionPatch>) -> Definition {
1356        Definition {
1357            version,
1358            localised_fields: vec![],
1359            fields: vec![],
1360            localised_key_order: vec![],
1361            patches: schema_patches.cloned().unwrap_or_default(),
1362            fields_processed: vec![],
1363        }
1364    }
1365
1366    /// Creates a new definition with the specified fields.
1367    ///
1368    /// # Arguments
1369    ///
1370    /// * `version` - The version number for this definition
1371    /// * `fields` - The table's field list
1372    /// * `loc_fields` - The localised fields list
1373    /// * `schema_patches` - Optional patches to apply to this definition
1374    ///
1375    /// # Returns
1376    ///
1377    /// Returns a new definition with the provided fields.
1378    pub fn new_with_fields(version: i32, fields: &[Field], loc_fields: &[Field], schema_patches: Option<&DefinitionPatch>) -> Definition {
1379        let mut definition = Definition {
1380            version,
1381            localised_fields: loc_fields.to_vec(),
1382            fields: fields.to_vec(),
1383            localised_key_order: vec![],
1384            patches: schema_patches.cloned().unwrap_or_default(),
1385            fields_processed: vec![],
1386        };
1387
1388        definition.update_fields_processed();
1389        definition
1390    }
1391
1392    /// Replaces the fields of the definition, rebuilding its processed fields.
1393    ///
1394    /// # Arguments
1395    ///
1396    /// * `fields` - The new raw field list
1397    pub fn set_fields(&mut self, fields: Vec<Field>) {
1398        self.fields = fields;
1399        self.update_fields_processed();
1400    }
1401
1402    /// Edits the fields of the definition in place, rebuilding its processed fields afterwards.
1403    ///
1404    /// # Arguments
1405    ///
1406    /// * `edit` - Closure receiving the raw field list to edit
1407    pub fn edit_fields(&mut self, edit: impl FnOnce(&mut Vec<Field>)) {
1408        edit(&mut self.fields);
1409        self.update_fields_processed();
1410    }
1411
1412    /// Replaces the patches of the definition, rebuilding its processed fields.
1413    ///
1414    /// # Arguments
1415    ///
1416    /// * `patches` - The new patches
1417    pub fn set_patches(&mut self, patches: DefinitionPatch) {
1418        self.patches = patches;
1419        self.update_fields_processed();
1420    }
1421
1422    /// Edits the patches of the definition in place, rebuilding its processed fields afterwards.
1423    ///
1424    /// # Arguments
1425    ///
1426    /// * `edit` - Closure receiving the patches to edit
1427    pub fn edit_patches(&mut self, edit: impl FnOnce(&mut DefinitionPatch)) {
1428        edit(&mut self.patches);
1429        self.update_fields_processed();
1430    }
1431
1432    /// Returns a view of the definition with only what schema files store, to serialize it.
1433    pub fn stored(&self) -> StoredDefinition<'_> {
1434        StoredDefinition {
1435            version: self.version,
1436            fields: self.fields.iter().map(Field::stored).collect(),
1437            localised_fields: self.localised_fields.iter().map(Field::stored).collect(),
1438            localised_key_order: &self.localised_key_order,
1439        }
1440    }
1441
1442    /// Returns reference and lookup information for all fields with foreign key references.
1443    ///
1444    /// This function extracts foreign key information from all fields in the definition
1445    /// that have a reference to another table.
1446    ///
1447    /// # Returns
1448    ///
1449    /// Returns a map where:
1450    /// - Keys are field indices (as [`i32`])
1451    /// - Values are tuples of `(referenced_table, referenced_column, optional_lookup_columns)`
1452    ///
1453    /// Only fields with `is_reference` set are included in the result.
1454    pub fn reference_data(&self) -> BTreeMap<i32, (String, String, Option<Vec<String>>)> {
1455        self.fields.iter()
1456            .enumerate()
1457            .filter(|x| x.1.is_reference.is_some())
1458            .map(|x| (x.0 as i32, (x.1.is_reference.clone().unwrap().0, x.1.is_reference.clone().unwrap().1, x.1.lookup.clone())))
1459            .collect()
1460    }
1461
1462    /// Returns the processed field list with transformations applied.
1463    ///
1464    /// The raw field list goes through these transformations:
1465    /// - **Bitwise fields**: Expanded into multiple boolean fields (e.g., `flags` → `flags_1`, `flags_2`, etc.)
1466    /// - **Enum fields**: Converted to StringU8 fields
1467    /// - **Colour fields**: RGB triplets merged into single ColourRGB fields
1468    /// - **Numeric fields**: Converted to I32 fields (with patches)
1469    /// - **Patches**: Applied to the resulting fields, by their processed name
1470    ///
1471    /// This is the field list that should be used for UI display and data editing.
1472    ///
1473    /// # Returns
1474    ///
1475    /// Returns the processed field list with all transformations applied.
1476    pub fn fields_processed(&self) -> &[Field] {
1477        &self.fields_processed
1478    }
1479
1480    /// Rebuilds the processed fields from the raw fields and the patches.
1481    fn update_fields_processed(&mut self) {
1482        let mut split_colour_fields: BTreeMap<u8, Field> = BTreeMap::new();
1483        let patches = Some(self.patches());
1484        let mut fields = self.fields().iter()
1485            .filter_map(|x|
1486                if x.is_bitwise() > 1 {
1487                    let unused = x.unused(patches);
1488                    let mut fields = vec![x.clone(); x.is_bitwise() as usize];
1489                    fields.iter_mut().enumerate().for_each(|(index, field)| {
1490                        field.set_name(format!("{}_{}", field.name(), index + 1));
1491                        field.set_field_type(FieldType::Boolean);
1492                        field.set_unused(unused);
1493                    });
1494                    Some(fields)
1495                }
1496
1497                else if !x.enum_values().is_empty() {
1498                    let mut field = x.clone();
1499                    field.set_field_type(FieldType::StringU8);
1500                    Some(vec![field; 1])
1501                }
1502
1503                else if let Some(colour_index) = x.is_part_of_colour() {
1504                    match split_colour_fields.get_mut(&colour_index) {
1505
1506                        // If found, add the default value to the other previously known default value.
1507                        Some(field) => {
1508                            let default_value = match x.default_value(None) {
1509                                Some(default_value) => {
1510                                    if x.name.ends_with("_r") || x.name.ends_with("_red") || x.name == "r" || x.name == "red" {
1511                                        field.default_value.clone().map(|df| {
1512                                            format!("{:X}{}", default_value.parse::<i32>().unwrap_or(0), &df[2..])
1513                                        })
1514                                    } else if x.name.ends_with("_g") || x.name.ends_with("_green") || x.name == "g" || x.name == "green" {
1515                                        field.default_value.clone().map(|df| {
1516                                            format!("{}{:X}{}", &df[..2], default_value.parse::<i32>().unwrap_or(0), &df[4..])
1517                                        })
1518                                    } else if x.name.ends_with("_b") || x.name.ends_with("_blue") || x.name == "b" || x.name == "blue" {
1519                                        field.default_value.clone().map(|df| {
1520                                            format!("{}{:X}", &df[..4], default_value.parse::<i32>().unwrap_or(0))
1521                                        })
1522                                    } else {
1523                                        Some("000000".to_owned())
1524                                    }
1525                                }
1526                                None => Some("000000".to_owned())
1527                            };
1528
1529                            // Update the default value with the one for this colour.
1530                            field.set_default_value(default_value);
1531
1532                            if !field.unused(patches) {
1533                                field.set_unused(x.unused(patches));
1534                            }
1535                        },
1536                        None => {
1537                            let unused = x.unused(patches);
1538                            let colour_split = x.name().rsplitn(2, '_').collect::<Vec<&str>>();
1539                            let colour_field_name = if colour_split.len() == 2 {
1540                                format!("{}{}", colour_split[1].to_lowercase(), MERGE_COLOUR_POST)
1541                            } else {
1542                                format!("{}_{}", MERGE_COLOUR_NO_NAME.to_lowercase(), colour_index)
1543                            };
1544
1545                            let mut field = x.clone();
1546                            field.set_name(colour_field_name);
1547                            field.set_field_type(FieldType::ColourRGB);
1548                            field.set_unused(unused);
1549
1550                            // We need to fix the default value so it's a ColourRGB one.
1551                            let default_value = match field.default_value(None) {
1552                                Some(default_value) => {
1553                                    if x.name.ends_with("_r") || x.name.ends_with("_red") || x.name == "r" || x.name == "red" {
1554                                        Some(format!("{:X}0000", default_value.parse::<i32>().unwrap_or(0)))
1555                                    } else if x.name.ends_with("_g") || x.name.ends_with("_green") || x.name == "g" || x.name == "green" {
1556                                        Some(format!("00{:X}00", default_value.parse::<i32>().unwrap_or(0)))
1557                                    } else if x.name.ends_with("_b") || x.name.ends_with("_blue") || x.name == "b" || x.name == "blue" {
1558                                        Some(format!("0000{:X}", default_value.parse::<i32>().unwrap_or(0)))
1559                                    } else {
1560                                        Some("000000".to_owned())
1561                                    }
1562                                }
1563                                None => Some("000000".to_owned())
1564                            };
1565
1566                            field.set_default_value(default_value);
1567
1568                            split_colour_fields.insert(colour_index, field);
1569                        }
1570                    }
1571
1572                    None
1573                }
1574
1575                else if x.is_numeric(patches) {
1576                    let mut field = x.clone();
1577                    field.set_field_type(FieldType::I32);
1578                    Some(vec![field; 1])
1579                }
1580
1581                else {
1582                    Some(vec![x.clone(); 1])
1583                }
1584            )
1585            .flatten()
1586            .collect::<Vec<Field>>();
1587
1588        // Second pass to add the combined colour fields.
1589        fields.append(&mut split_colour_fields.values().cloned().collect::<Vec<Field>>());
1590
1591        // Third pass to bake the patches into the fields, so they don't need the patches to be read.
1592        for field in &mut fields {
1593            field.is_key = field.is_key(patches);
1594            field.default_value = field.default_value(patches);
1595            field.is_filename = field.is_filename(patches);
1596            field.filename_relative_path = field.filename_relative_path(patches).map(|paths| paths.join(";"));
1597            field.is_reference = field.is_reference(patches);
1598            field.lookup = field.lookup(patches);
1599            field.description = field.description(patches);
1600            field.unused = field.unused(patches);
1601        }
1602
1603        self.fields_processed = fields;
1604    }
1605
1606    /// Returns the original raw field corresponding to a processed field index.
1607    ///
1608    /// This function maps a field from the processed field list back to its original
1609    /// raw field definition. This is useful when you need to access the underlying
1610    /// field data before transformations like bitwise expansion.
1611    ///
1612    /// # Arguments
1613    ///
1614    /// * `index` - Index in the processed field list
1615    ///
1616    /// # Returns
1617    ///
1618    /// Returns the original field from the raw field list.
1619    ///
1620    /// # Panics
1621    ///
1622    /// Panics if the field is not found (which should never happen for valid indices).
1623    ///
1624    /// # Note
1625    ///
1626    /// This function does not work correctly with combined colour fields, as they don't
1627    /// have a direct 1:1 mapping to a single raw field.
1628    pub fn original_field_from_processed(&self, index: usize) -> Field {
1629        let fields = self.fields();
1630        let processed = self.fields_processed();
1631
1632        let field_processed = &processed[index];
1633        let name = if field_processed.is_bitwise() > 1 {
1634            let mut name = field_processed.name().to_owned();
1635            name.drain(..name.rfind('_').unwrap()).collect::<String>()
1636        }
1637        else {field_processed.name().to_owned() };
1638
1639        fields.iter().find(|x| *x.name() == name).unwrap().clone()
1640    }
1641
1642    /// Returns the processed field list sorted by either key fields or CA order.
1643    ///
1644    /// This function returns the processed fields sorted according to the specified criteria.
1645    ///
1646    /// # Arguments
1647    ///
1648    /// * `key_first` - If `true`, sorts key fields first, then non-key fields. If `false`, sorts by CA order.
1649    ///
1650    /// # Returns
1651    ///
1652    /// Returns the sorted field list. Fields with `ca_order == -1` are left in their original order
1653    /// when sorting by CA order.
1654    pub fn fields_processed_sorted(&self, key_first: bool) -> Vec<Field> {
1655        let mut fields = self.fields_processed().to_vec();
1656        let patches = Some(self.patches());
1657        fields.sort_by(|a, b| {
1658            if key_first {
1659                if a.is_key(patches) && b.is_key(patches) { Ordering::Equal }
1660                else if a.is_key(patches) && !b.is_key(patches) { Ordering::Less }
1661                else if !a.is_key(patches) && b.is_key(patches) { Ordering::Greater }
1662                else { Ordering::Equal }
1663            }
1664            else if a.ca_order() == -1 || b.ca_order() == -1 { Ordering::Equal }
1665            else { a.ca_order().cmp(&b.ca_order()) }
1666        });
1667        fields
1668    }
1669
1670    /// Returns the position of a column in the processed field list by name.
1671    ///
1672    /// # Arguments
1673    ///
1674    /// * `column_name` - Name of the column to find
1675    ///
1676    /// # Returns
1677    ///
1678    /// Returns the column's index in the processed field list, or [`None`] if not found.
1679    pub fn column_position_by_name(&self, column_name: &str) -> Option<usize> {
1680        self.fields_processed()
1681            .iter()
1682            .position(|x| x.name() == column_name)
1683    }
1684
1685    /// Returns the positions of all key columns in the processed field list.
1686    ///
1687    /// # Returns
1688    ///
1689    /// Returns a vector of indices for all fields marked as key fields.
1690    pub fn key_column_positions(&self) -> Vec<usize> {
1691        self.fields_processed()
1692            .iter()
1693            .enumerate()
1694            .filter(|(_, x)| x.is_key(Some(self.patches())))
1695            .map(|(x, _)| x)
1696            .collect::<Vec<_>>()
1697    }
1698
1699    /// Returns the positions of all key columns sorted by CA order.
1700    ///
1701    /// This function returns key column positions in the same order as they appear in
1702    /// CA's Assembly Kit, rather than the binary order. This is primarily needed for
1703    /// `twad_key_deletes` functionality, which uses CA's ordering.
1704    ///
1705    /// # Returns
1706    ///
1707    /// Returns a vector of key column indices sorted by their `ca_order` value.
1708    pub fn key_column_positions_by_ca_order(&self) -> Vec<usize> {
1709        let fields_processed = self.fields_processed();
1710        let mut keys = fields_processed
1711            .iter()
1712            .enumerate()
1713            .filter(|(_, x)| x.is_key(Some(self.patches())))
1714            .map(|(x, _)| x)
1715            .collect::<Vec<_>>();
1716
1717        keys.sort_by_key(|x| fields_processed[*x].ca_order);
1718        keys
1719    }
1720
1721    /// Generates a SQL `CREATE TABLE` statement for this definition.
1722    ///
1723    /// This function creates a SQL statement suitable for creating a table in SQLite
1724    /// with the structure defined by this definition. The table includes additional
1725    /// metadata columns (`pack_name`, `file_name`, `is_vanilla`) for tracking data sources.
1726    ///
1727    /// # Arguments
1728    ///
1729    /// * `table_name` - Name for the SQL table
1730    ///
1731    /// # Returns
1732    ///
1733    /// Returns the SQL `CREATE TABLE` statement as a string.
1734    ///
1735    /// # Note
1736    ///
1737    /// Foreign key constraints are intentionally disabled because Total War tables
1738    /// (especially in mods) often have referential integrity issues. The function
1739    /// only creates a primary key constraint on the key fields.
1740    ///
1741    /// # Feature
1742    ///
1743    /// This function requires the `integration_sqlite` feature.
1744    #[cfg(feature = "integration_sqlite")]
1745    pub fn map_to_sql_create_table_string(&self, table_name: &str) -> String {
1746        let patches = Some(self.patches());
1747        let fields_sorted = self.fields_processed();
1748        let fields_query = fields_sorted.iter().map(|field| field.map_to_sql_string(patches)).collect::<Vec<_>>().join(",");
1749
1750        let local_keys_join = fields_sorted.iter().filter_map(|field| if field.is_key(patches) { Some(format!("\"{}\"", field.name()))} else { None }).collect::<Vec<_>>().join(",");
1751        let local_keys = format!("CONSTRAINT unique_key PRIMARY KEY (\"pack_name\", \"file_name\", {local_keys_join})");
1752        //let foreign_keys = fields_sorted.iter()
1753        //    .filter_map(|field| field.is_reference(patches).clone().map(|(ref_table, ref_column)| (field.name(), ref_table, ref_column)))
1754        //    .map(|(loc_name, ref_table, ref_field)| format!("CONSTRAINT fk_{table_name} FOREIGN KEY (\"{loc_name}\") REFERENCES {ref_table}(\"{ref_field}\") ON UPDATE CASCADE ON DELETE CASCADE"))
1755        //    .collect::<Vec<_>>()
1756        //    .join(",");
1757
1758        //if foreign_keys.is_empty() {
1759            if local_keys_join.is_empty() {
1760                format!("CREATE TABLE \"{}_v{}\" (\"pack_name\" STRING NOT NULL, \"file_name\" STRING NOT NULL, \"is_vanilla\" INTEGER DEFAULT 0, {})",
1761                    table_name.replace('\"', "'"),
1762                    self.version(),
1763                    fields_query
1764                )
1765            } else {
1766                format!("CREATE TABLE \"{}_v{}\" (\"pack_name\" STRING NOT NULL, \"file_name\" STRING NOT NULL, \"is_vanilla\" INTEGER DEFAULT 0, {}, {})",
1767                    table_name.replace('\"', "'"),
1768                    self.version(),
1769                    fields_query,
1770                    local_keys
1771                )
1772            }
1773        /*} else if local_keys_join.is_empty() {
1774            format!("CREATE TABLE \"{}_v{}\" (\"table_unique_id\" INTEGER DEFAULT 0, {}, {})",
1775                table_name.replace('\"', "'"),
1776                self.version(),
1777                fields_query,
1778                foreign_keys
1779            )
1780        } else {
1781            format!("CREATE TABLE \"{}_v{}\" (\"table_unique_id\" INTEGER DEFAULT 0, {}, {}, {})",
1782                table_name.replace('\"', "'"),
1783                self.version(),
1784                fields_query,
1785                local_keys,
1786                foreign_keys
1787            )
1788        }*/
1789    }
1790
1791    /// Generates the column list for a SQL `INSERT INTO` statement.
1792    ///
1793    /// This function creates the column name list portion of an `INSERT INTO` statement,
1794    /// including the metadata columns and all processed fields.
1795    ///
1796    /// # Returns
1797    ///
1798    /// Returns a string like `("pack_name", "file_name", "is_vanilla", "field1", "field2", ...)`.
1799    ///
1800    /// # Feature
1801    ///
1802    /// This function requires the `integration_sqlite` feature.
1803    #[cfg(feature = "integration_sqlite")]
1804    pub fn map_to_sql_insert_into_string(&self) -> String {
1805        let fields_sorted = self.fields_processed();
1806        let fields_query = fields_sorted.iter().map(|field| format!("\"{}\"", field.name())).collect::<Vec<_>>().join(",");
1807        let fields_query = format!("(\"pack_name\", \"file_name\", \"is_vanilla\", {fields_query})");
1808
1809        fields_query
1810    }
1811
1812    /// Updates field properties from Assembly Kit raw definition data.
1813    ///
1814    /// This function updates the definition's fields with data extracted from the Assembly Kit,
1815    /// matching fields by name and updating specific properties. Fields not found in the
1816    /// Assembly Kit are added to the `unfound_fields` list for reporting.
1817    ///
1818    /// # Updated Properties
1819    ///
1820    /// - `is_key`: Primary key status
1821    /// - `default_value`: Default value for new rows
1822    /// - `filename_relative_path`: Path hints for filename fields
1823    /// - `is_filename`: Whether the field contains a filename
1824    /// - `is_reference`: Foreign key reference information
1825    /// - `lookup`: Lookup column information
1826    /// - `description`: Field description
1827    /// - `ca_order`: Visual position in Assembly Kit
1828    /// - `is_part_of_colour`: Auto-detected RGB colour field grouping
1829    ///
1830    /// # Arguments
1831    ///
1832    /// * `raw_definition` - The Assembly Kit definition data
1833    /// * `unfound_fields` - List to append unfound field names to (format: `"table_name/field_name"`)
1834    ///
1835    /// # Note
1836    ///
1837    /// Fields in `IGNORABLE_FIELDS` are automatically skipped and not reported as unfound.
1838    ///
1839    /// # Feature
1840    ///
1841    /// This function requires the `integration_assembly_kit` feature.
1842    #[cfg(feature = "integration_assembly_kit")]
1843    pub fn update_from_raw_definition(&mut self, raw_definition: &RawDefinition, unfound_fields: &mut Vec<String>) {
1844        let raw_table_name = &raw_definition.name.as_ref().unwrap()[..raw_definition.name.as_ref().unwrap().len() - 4];
1845        let mut combined_fields = BTreeMap::new();
1846        for (index, raw_field) in raw_definition.fields.iter().enumerate() {
1847
1848            let mut found = false;
1849            for field in &mut self.fields {
1850                if field.name == raw_field.name {
1851                    if (raw_field.primary_key == "1" && !field.is_key) || (raw_field.primary_key == "0" && field.is_key) {
1852                        field.is_key = raw_field.primary_key == "1";
1853                    }
1854
1855                    if raw_field.default_value.is_some() {
1856                        field.default_value = raw_field.default_value.clone();
1857                    }
1858
1859                    if let Some(ref path) = raw_field.filename_relative_path {
1860                        let mut new_path = path.to_owned();
1861                        if path.contains(",") {
1862                            new_path = path.split(',').map(|x| x.trim()).join(";");
1863                        }
1864
1865                        field.filename_relative_path = Some(new_path);
1866                    }
1867
1868                    // Some fields are marked as filename, but only have fragment paths, which do not seem to correlate to game file paths.
1869                    // We need to disable those to avoid false positives on diagnostics.
1870                    field.is_filename = match raw_field.is_filename {
1871                        Some(_) => !(raw_field.fragment_path.is_some() && raw_field.filename_relative_path.is_none()),
1872                        None => false,
1873                    };
1874
1875                    // Make sure to cleanup any old invalid definition.
1876                    if let Some(ref description) = raw_field.field_description {
1877                        field.description = description.to_owned();
1878                    } else {
1879                        field.description = String::new();
1880                    }
1881
1882                    // We reset these so we don't inherit wrong references from older tables.
1883                    field.is_reference = Default::default();
1884                    field.lookup = Default::default();
1885                    if let Some(ref table) = raw_field.column_source_table {
1886                        if let Some(ref columns) = raw_field.column_source_column {
1887                            if !table.is_empty() && !columns.is_empty() && !columns[0].is_empty() {
1888                                field.is_reference = Some((table.to_owned(), columns[0].to_owned()));
1889                                if columns.len() > 1 {
1890                                    field.lookup = Some(columns[1..].to_vec());
1891                                }
1892                            }
1893                        }
1894                    }
1895
1896                    field.ca_order = index as i16;
1897
1898                    // Detect and group colour fields.
1899                    let is_numeric = matches!(field.field_type, FieldType::I16 | FieldType::I32 | FieldType::I64 | FieldType::F32 | FieldType::F64);
1900
1901                    if is_numeric && (
1902                        field.name.ends_with("_r") ||
1903                        field.name.ends_with("_g") ||
1904                        field.name.ends_with("_b") ||
1905                        field.name.ends_with("_red") ||
1906                        field.name.ends_with("_green") ||
1907                        field.name.ends_with("_blue") ||
1908                        field.name == "r" ||
1909                        field.name == "g" ||
1910                        field.name == "b" ||
1911                        field.name == "red" ||
1912                        field.name == "green" ||
1913                        field.name == "blue"
1914                    ) {
1915                        let colour_split = field.name.rsplitn(2, '_').collect::<Vec<&str>>();
1916                        let colour_field_name = if colour_split.len() == 2 { format!("{}{}", colour_split[1].to_lowercase(), MERGE_COLOUR_POST) } else { MERGE_COLOUR_NO_NAME.to_lowercase() };
1917
1918                        match combined_fields.get(&colour_field_name) {
1919                            Some(group_key) => field.is_part_of_colour = Some(*group_key),
1920                            None => {
1921                                let group_key = combined_fields.keys().len() as u8 + 1;
1922                                combined_fields.insert(colour_field_name.to_owned(), group_key);
1923                                field.is_part_of_colour = Some(group_key);
1924                            }
1925                        }
1926                    }
1927                    found = true;
1928                    break;
1929                }
1930            }
1931
1932            if !found {
1933
1934                // We need to check if it's a loc field before reporting it as unfound.
1935                for loc_field in self.localised_fields() {
1936                    if loc_field.name == raw_field.name {
1937                        found = true;
1938                        break;
1939                    }
1940                }
1941
1942                // We automatically ignore certain old fields that have nothing to do with the game's data.
1943                if !found && !IGNORABLE_FIELDS.contains(&&*raw_field.name) {
1944                    unfound_fields.push(format!("{}/{}", raw_table_name, raw_field.name));
1945                }
1946            }
1947        }
1948
1949        self.update_fields_processed();
1950    }
1951
1952    /// Populates the `localised_fields` list from Assembly Kit data.
1953    ///
1954    /// This function identifies fields that should be extracted to LOC files based on
1955    /// Assembly Kit localisable field data and updates the definition's `localised_fields` list.
1956    /// All identified localised fields are set to [`FieldType::StringU8`] for consistency.
1957    ///
1958    /// # Arguments
1959    ///
1960    /// * `raw_definition` - The Assembly Kit table definition
1961    /// * `raw_localisable_fields` - List of all localisable fields from the Assembly Kit
1962    ///
1963    /// # Feature
1964    ///
1965    /// This function requires the `integration_assembly_kit` feature.
1966    #[cfg(feature = "integration_assembly_kit")]
1967    pub fn update_from_raw_localisable_fields(&mut self, raw_definition: &RawDefinition, raw_localisable_fields: &[RawLocalisableField]) {
1968        let raw_table_name = &raw_definition.name.as_ref().unwrap()[..raw_definition.name.as_ref().unwrap().len() - 4];
1969        let localisable_fields_names = raw_localisable_fields.iter()
1970            .filter(|x| x.table_name == raw_table_name)
1971            .map(|x| &*x.field)
1972            .collect::<Vec<&str>>();
1973
1974        if !localisable_fields_names.is_empty() {
1975            let localisable_fields = raw_definition.fields.iter()
1976                .filter(|x| localisable_fields_names.contains(&&*x.name))
1977                .collect::<Vec<&RawField>>();
1978
1979            self.localised_fields = localisable_fields.iter().map(|x| From::from(*x)).collect();
1980
1981            // Set their type to StringU8 for consistency.
1982            self.localised_fields.iter_mut().for_each(|field| field.field_type = FieldType::StringU8);
1983        }
1984    }
1985}
1986
1987/// Implementation of `Field`.
1988impl Field {
1989
1990    //----------------------------------------------------------------------//
1991    // Manual getter implementations with patch support
1992    //----------------------------------------------------------------------//
1993
1994    /// Returns the field name.
1995    pub fn name(&self) -> &str {
1996        &self.name
1997    }
1998
1999    /// Returns the field's data type.
2000    pub fn field_type(&self) -> &FieldType {
2001        &self.field_type
2002    }
2003
2004    /// Returns whether this field is a key field, applying patches if provided.
2005    ///
2006    /// # Arguments
2007    ///
2008    /// * `schema_patches` - Optional patches to check for overrides
2009    ///
2010    /// # Returns
2011    ///
2012    /// Returns `true` if the field is a key field (either by base definition or patch).
2013    pub fn is_key(&self, schema_patches: Option<&DefinitionPatch>) -> bool {
2014        if let Some(schema_patches) = schema_patches {
2015            if let Some(patch) = schema_patches.get(self.name()) {
2016                if let Some(field_patch) = patch.get("is_key") {
2017                    return field_patch.parse().unwrap_or(false);
2018                }
2019            }
2020        }
2021
2022        self.is_key
2023    }
2024
2025    /// Returns the field's default value, applying patches if provided.
2026    ///
2027    /// # Arguments
2028    ///
2029    /// * `schema_patches` - Optional patches to check for overrides
2030    ///
2031    /// # Returns
2032    ///
2033    /// Returns the default value if set (either by base definition or patch).
2034    pub fn default_value(&self, schema_patches: Option<&DefinitionPatch>) -> Option<String> {
2035        if let Some(schema_patches) = schema_patches {
2036            if let Some(patch) = schema_patches.get(self.name()) {
2037                if let Some(field_patch) = patch.get("default_value") {
2038                    return Some(field_patch.to_string());
2039                }
2040            }
2041        }
2042
2043        self.default_value.clone()
2044    }
2045
2046    /// Returns whether this field contains a filename, applying patches if provided.
2047    ///
2048    /// # Arguments
2049    ///
2050    /// * `schema_patches` - Optional patches to check for overrides
2051    ///
2052    /// # Returns
2053    ///
2054    /// Returns `true` if the field contains a filename path.
2055    pub fn is_filename(&self, schema_patches: Option<&DefinitionPatch>) -> bool {
2056        if let Some(schema_patches) = schema_patches {
2057            if let Some(patch) = schema_patches.get(self.name()) {
2058                if let Some(field_patch) = patch.get("is_filename") {
2059                    return field_patch.parse().unwrap_or(false);
2060                }
2061            }
2062        }
2063
2064        self.is_filename
2065    }
2066
2067    /// Returns the filename relative paths, applying patches if provided.
2068    ///
2069    /// The paths are split by semicolons and backslashes are converted to forward slashes.
2070    ///
2071    /// # Arguments
2072    ///
2073    /// * `schema_patches` - Optional patches to check for overrides
2074    ///
2075    /// # Returns
2076    ///
2077    /// Returns a vector of relative path strings, or [`None`] if no paths are defined.
2078    pub fn filename_relative_path(&self, schema_patches: Option<&DefinitionPatch>) -> Option<Vec<String>> {
2079        if let Some(schema_patches) = schema_patches {
2080            if let Some(patch) = schema_patches.get(self.name()) {
2081                if let Some(field_patch) = patch.get("filename_relative_path") {
2082                    return Some(field_patch.replace('\\', "/").split(';').map(|x| x.to_string()).collect::<Vec<String>>());
2083                }
2084            }
2085        }
2086
2087        self.filename_relative_path.clone().map(|x| x.replace('\\', "/").split(';').map(|x| x.to_string()).collect::<Vec<String>>())
2088    }
2089
2090    /// Returns the foreign key reference information, applying patches if provided.
2091    ///
2092    /// # Arguments
2093    ///
2094    /// * `schema_patches` - Optional patches to check for overrides
2095    ///
2096    /// # Returns
2097    ///
2098    /// Returns `Some((table_name, column_name))` if this field references another table,
2099    /// or [`None`] if it doesn't. The table name does not include the `_tables` suffix.
2100    pub fn is_reference(&self, schema_patches: Option<&DefinitionPatch>) -> Option<(String,String)> {
2101        if let Some(schema_patches) = schema_patches {
2102            if let Some(patch) = schema_patches.get(self.name()) {
2103                if let Some(field_patch) = patch.get("is_reference") {
2104                    let split = field_patch.splitn(2, ';').collect::<Vec<_>>();
2105                    if split.len() == 2 {
2106                        return Some((split[0].to_string(), split[1].to_string()));
2107                    }
2108                }
2109            }
2110        }
2111
2112        self.is_reference.clone()
2113    }
2114
2115    /// Returns the lookup column list, applying patches if provided.
2116    ///
2117    /// Lookup columns are additional columns from the referenced table that should
2118    /// be displayed in the UI alongside the referenced field.
2119    ///
2120    /// # Arguments
2121    ///
2122    /// * `schema_patches` - Optional patches to check for overrides
2123    ///
2124    /// # Returns
2125    ///
2126    /// Returns a vector of column names to look up, or [`None`] if no lookups are defined.
2127    pub fn lookup(&self, schema_patches: Option<&DefinitionPatch>) -> Option<Vec<String>> {
2128        if let Some(schema_patches) = schema_patches {
2129            if let Some(patch) = schema_patches.get(self.name()) {
2130                if let Some(field_patch) = patch.get("lookup") {
2131                    return Some(field_patch.split(';').map(|x| x.to_string()).collect());
2132                }
2133            }
2134        }
2135
2136        self.lookup.clone()
2137    }
2138
2139    /// Returns the lookup column list without applying patches.
2140    ///
2141    /// # Returns
2142    ///
2143    /// Returns a vector of column names from the base definition, ignoring any patches.
2144    pub fn lookup_no_patch(&self) -> Option<Vec<String>> {
2145        self.lookup.clone()
2146    }
2147
2148    /// Returns hardcoded lookup values from patches.
2149    ///
2150    /// Hardcoded lookups provide predefined value mappings that don't require
2151    /// querying the referenced table. This is useful for performance or when
2152    /// the referenced table is not available.
2153    ///
2154    /// # Arguments
2155    ///
2156    /// * `schema_patches` - Optional patches to check for hardcoded values
2157    ///
2158    /// # Returns
2159    ///
2160    /// Returns a map of key values to their display strings. Returns an empty
2161    /// map if no hardcoded lookups are defined.
2162    pub fn lookup_hardcoded(&self, schema_patches: Option<&DefinitionPatch>) -> HashMap<String, String> {
2163        if let Some(schema_patches) = schema_patches {
2164            if let Some(patch) = schema_patches.get(self.name()) {
2165                if let Some(field_patch) = patch.get("lookup_hardcoded") {
2166                    let entries = field_patch.split(":::::").map(|x| x.split(";;;;;").collect::<Vec<_>>()).collect::<Vec<_>>();
2167                    let mut hashmap = HashMap::new();
2168                    for entry in entries {
2169                        hashmap.insert(entry[0].to_owned(), entry[1].to_owned());
2170                    }
2171                    return hashmap;
2172                }
2173            }
2174        }
2175
2176        HashMap::new()
2177    }
2178
2179    /// Returns the field description, applying patches if provided.
2180    ///
2181    /// # Arguments
2182    ///
2183    /// * `schema_patches` - Optional patches to check for overrides
2184    ///
2185    /// # Returns
2186    ///
2187    /// Returns the field's description text. May be empty if no description is set.
2188    pub fn description(&self, schema_patches: Option<&DefinitionPatch>) -> String {
2189        if let Some(schema_patches) = schema_patches {
2190            if let Some(patch) = schema_patches.get(self.name()) {
2191                if let Some(field_patch) = patch.get("description") {
2192                    return field_patch.to_owned();
2193                }
2194            }
2195        }
2196
2197        self.description.to_owned()
2198    }
2199
2200    /// Returns the CA order value.
2201    ///
2202    /// This represents the visual position of the field in CA's Assembly Kit.
2203    /// A value of `-1` indicates the position is unknown.
2204    pub fn ca_order(&self) ->  i16 {
2205        self.ca_order
2206    }
2207
2208    /// Returns the bitwise expansion count.
2209    ///
2210    /// # Returns
2211    ///
2212    /// - `0` or `1`: No bitwise expansion
2213    /// - `> 1`: Number of boolean columns this field should be expanded into
2214    pub fn is_bitwise(&self) -> i32 {
2215        self.is_bitwise
2216    }
2217
2218    /// Returns the enum value mappings.
2219    ///
2220    /// # Returns
2221    ///
2222    /// Returns a reference to the map of integer values to their string names.
2223    /// Empty if this field is not an enum.
2224    pub fn enum_values(&self) -> &BTreeMap<i32,String> {
2225        &self.enum_values
2226    }
2227
2228    /// Returns the enum values as an [`Option`].
2229    pub fn enum_values_to_option(&self) -> Option<BTreeMap<i32, String>> {
2230        if self.enum_values.is_empty() { None }
2231        else { Some(self.enum_values.clone()) }
2232    }
2233
2234    /// Returns the enum values as a semicolon-separated string.
2235    ///
2236    /// # Returns
2237    ///
2238    /// Returns a string in the format `"value1,name1;value2,name2;..."`.
2239    pub fn enum_values_to_string(&self) -> String {
2240        self.enum_values.iter().map(|(x, y)| format!("{x},{y}")).collect::<Vec<String>>().join(";")
2241    }
2242
2243    /// Returns the RGB colour group index.
2244    ///
2245    /// # Returns
2246    ///
2247    /// Returns the colour group index if this field is part of an RGB triplet,
2248    /// or [`None`] if it's not a colour field.
2249    pub fn is_part_of_colour(&self) -> Option<u8>{
2250        self.is_part_of_colour
2251    }
2252
2253    /// Returns whether this field should be treated as numeric (currently always `false`).
2254    ///
2255    /// This is a placeholder for future functionality and currently always returns `false`.
2256    ///
2257    /// # Arguments
2258    ///
2259    /// * `_schema_patches` - Unused (reserved for future use)
2260    pub fn is_numeric(&self, _schema_patches: Option<&DefinitionPatch>) -> bool {
2261        false
2262        /*
2263        if let Some(schema_patches) = schema_patches {
2264            if let Some(patch) = schema_patches.get(self.name()) {
2265                if let Some(is_numeric) = patch.get("is_numeric") {
2266                    return is_numeric.parse::<bool>().unwrap_or(false);
2267                }
2268            }
2269        }
2270
2271        false*/
2272    }
2273
2274    /// Returns whether this field cannot be empty, checking patches.
2275    ///
2276    /// # Arguments
2277    ///
2278    /// * `schema_patches` - Optional patches to check for the `not_empty` flag
2279    ///
2280    /// # Returns
2281    ///
2282    /// Returns `true` if the field is marked as "cannot be empty" via a patch.
2283    pub fn cannot_be_empty(&self, schema_patches: Option<&DefinitionPatch>) -> bool {
2284        if let Some(schema_patches) = schema_patches {
2285            if let Some(patch) = schema_patches.get(self.name()) {
2286                if let Some(cannot_be_empty) = patch.get("not_empty") {
2287                    return cannot_be_empty.parse::<bool>().unwrap_or(false);
2288                }
2289            }
2290        }
2291
2292        false
2293    }
2294
2295    /// Returns whether this field is unused by the game.
2296    ///
2297    /// Fields marked as unused are still present in the binary format but are not
2298    /// actually used by the game logic. This information is primarily determined via patches.
2299    ///
2300    /// # Arguments
2301    ///
2302    /// * `schema_patches` - Optional patches to check for the `unused` flag
2303    ///
2304    /// # Returns
2305    ///
2306    /// Returns `true` if the field is marked as unused (either in the base definition or via patch).
2307    pub fn unused(&self, schema_patches: Option<&DefinitionPatch>) -> bool {
2308
2309        // By default all fields are used, except the ones set through patches. If it's already marked unused, return early.
2310        self.unused || {
2311
2312            if let Some(schema_patches) = schema_patches {
2313                if let Some(patch) = schema_patches.get(self.name()) {
2314                    if let Some(cannot_be_empty) = patch.get("unused") {
2315                        return cannot_be_empty.parse::<bool>().unwrap_or(false);
2316                    }
2317                }
2318            }
2319
2320            false
2321        }
2322    }
2323
2324    /// Returns a view of the field with only what schema files store, to serialize it.
2325    fn stored(&self) -> StoredField<'_> {
2326        StoredField {
2327            name: &self.name,
2328            field_type: &self.field_type,
2329            is_key: self.is_key,
2330            default_value: &self.default_value,
2331            is_filename: self.is_filename,
2332            filename_relative_path: &self.filename_relative_path,
2333            is_reference: &self.is_reference,
2334            lookup: &self.lookup,
2335            description: &self.description,
2336            ca_order: self.ca_order,
2337            is_bitwise: self.is_bitwise,
2338            enum_values: &self.enum_values,
2339            is_part_of_colour: self.is_part_of_colour,
2340        }
2341    }
2342
2343    /// Generates a SQL column definition string for this field.
2344    ///
2345    /// This function creates the SQL column definition portion for use in a
2346    /// `CREATE TABLE` statement, including the data type and optional default value.
2347    ///
2348    /// # Arguments
2349    ///
2350    /// * `schema_patches` - Optional patches to apply when getting the default value
2351    ///
2352    /// # Returns
2353    ///
2354    /// Returns a string like `"field_name" INTEGER DEFAULT "value"`.
2355    ///
2356    /// # Feature
2357    ///
2358    /// This function requires the `integration_sqlite` feature.
2359    #[cfg(feature = "integration_sqlite")]
2360    pub fn map_to_sql_string(&self, schema_patches: Option<&DefinitionPatch>) -> String {
2361        let mut string = format!(" \"{}\" {:?} ", self.name(), self.field_type().map_to_sql_type());
2362
2363        if let Some(default_value) = self.default_value(schema_patches) {
2364            string.push_str(&format!(" DEFAULT \"{}\"", default_value.replace("\"", "\"\"")));
2365        }
2366
2367        string
2368    }
2369}
2370
2371impl FieldType {
2372
2373    /// Maps this field type to its corresponding SQLite type.
2374    ///
2375    /// This function converts RPFM's field types to their appropriate SQLite equivalents
2376    /// for database operations.
2377    ///
2378    /// # Returns
2379    ///
2380    /// Returns the SQLite [`Type`] that best represents this field type:
2381    /// - Numeric types → [`Type::Integer`] or [`Type::Real`]
2382    /// - String types → [`Type::Text`]
2383    /// - Sequence types → [`Type::Blob`]
2384    ///
2385    /// # Feature
2386    ///
2387    /// This function requires the `integration_sqlite` feature.
2388    #[cfg(feature = "integration_sqlite")]
2389    pub fn map_to_sql_type(&self) -> Type {
2390        match self {
2391            FieldType::Boolean => Type::Integer,
2392            FieldType::F32 => Type::Real,
2393            FieldType::F64 => Type::Real,
2394            FieldType::I16 => Type::Integer,
2395            FieldType::I32 => Type::Integer,
2396            FieldType::I64 => Type::Integer,
2397            FieldType::ColourRGB => Type::Text,
2398            FieldType::StringU8 => Type::Text,
2399            FieldType::StringU16 => Type::Text,
2400            FieldType::OptionalI16 => Type::Integer,
2401            FieldType::OptionalI32 => Type::Integer,
2402            FieldType::OptionalI64 => Type::Integer,
2403            FieldType::OptionalStringU8 => Type::Text,
2404            FieldType::OptionalStringU16 => Type::Text,
2405            FieldType::SequenceU16(_) => Type::Blob,
2406            FieldType::SequenceU32(_) => Type::Blob,
2407        }
2408    }
2409}
2410//---------------------------------------------------------------------------//
2411//                         Extra Implementations
2412//---------------------------------------------------------------------------//
2413
2414/// Default implementation of `Schema`.
2415impl Default for Schema {
2416    fn default() -> Self {
2417        Self {
2418            version: CURRENT_STRUCTURAL_VERSION,
2419            definitions: HashMap::new(),
2420            patches: HashMap::new()
2421        }
2422    }
2423}
2424
2425/// Default implementation of `FieldType`.
2426impl Default for Field {
2427    fn default() -> Self {
2428        Self {
2429            name: String::from("new_field"),
2430            field_type: FieldType::StringU8,
2431            is_key: false,
2432            default_value: None,
2433            is_filename: false,
2434            filename_relative_path: None,
2435            is_reference: None,
2436            lookup: None,
2437            description: String::new(),
2438            ca_order: -1,
2439            is_bitwise: 0,
2440            enum_values: BTreeMap::new(),
2441            is_part_of_colour: None,
2442            unused: false,
2443        }
2444    }
2445}
2446
2447/// Display implementation of `FieldType`.
2448impl Display for FieldType {
2449    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2450        match self {
2451            FieldType::Boolean => write!(f, "Boolean"),
2452            FieldType::F32 => write!(f, "F32"),
2453            FieldType::F64 => write!(f, "F64"),
2454            FieldType::I16 => write!(f, "I16"),
2455            FieldType::I32 => write!(f, "I32"),
2456            FieldType::I64 => write!(f, "I64"),
2457            FieldType::ColourRGB => write!(f, "ColourRGB"),
2458            FieldType::StringU8 => write!(f, "StringU8"),
2459            FieldType::StringU16 => write!(f, "StringU16"),
2460            FieldType::OptionalI16 => write!(f, "OptionalI16"),
2461            FieldType::OptionalI32 => write!(f, "OptionalI32"),
2462            FieldType::OptionalI64 => write!(f, "OptionalI64"),
2463            FieldType::OptionalStringU8 => write!(f, "OptionalStringU8"),
2464            FieldType::OptionalStringU16 => write!(f, "OptionalStringU16"),
2465            FieldType::SequenceU16(_) => write!(f, "SequenceU16"),
2466            FieldType::SequenceU32(_) => write!(f, "SequenceU32"),
2467        }
2468    }
2469}
2470
2471/// Implementation of `From<&RawDefinition>` for `Definition.
2472impl From<&DecodedData> for FieldType {
2473    fn from(data: &DecodedData) -> Self {
2474        match data {
2475            DecodedData::Boolean(_) => FieldType::Boolean,
2476            DecodedData::F32(_) => FieldType::F32,
2477            DecodedData::F64(_) => FieldType::F64,
2478            DecodedData::I16(_) => FieldType::I16,
2479            DecodedData::I32(_) => FieldType::I32,
2480            DecodedData::I64(_) => FieldType::I64,
2481            DecodedData::ColourRGB(_) => FieldType::ColourRGB,
2482            DecodedData::StringU8(_) => FieldType::StringU8,
2483            DecodedData::StringU16(_) => FieldType::StringU16,
2484            DecodedData::OptionalI16(_) => FieldType::OptionalI16,
2485            DecodedData::OptionalI32(_) => FieldType::OptionalI32,
2486            DecodedData::OptionalI64(_) => FieldType::OptionalI64,
2487            DecodedData::OptionalStringU8(_) => FieldType::OptionalStringU8,
2488            DecodedData::OptionalStringU16(_) => FieldType::OptionalStringU16,
2489            DecodedData::SequenceU16(_) => FieldType::SequenceU16(Box::new(Definition::new(INVALID_VERSION, None))),
2490            DecodedData::SequenceU32(_) => FieldType::SequenceU32(Box::new(Definition::new(INVALID_VERSION, None))),
2491        }
2492    }
2493}
2494
2495/// Builds a [`Definition`] from its deserialized data, rebuilding its processed fields.
2496impl From<DefinitionData> for Definition {
2497    fn from(data: DefinitionData) -> Self {
2498        let mut definition = Definition {
2499            version: data.version,
2500            fields: data.fields,
2501            localised_fields: data.localised_fields,
2502            localised_key_order: data.localised_key_order,
2503            patches: data.patches,
2504            fields_processed: vec![],
2505        };
2506
2507        definition.update_fields_processed();
2508        definition
2509    }
2510}
2511
2512/// Special serializer function to sort the definitions HashMap before serializing, storing only their raw parts.
2513fn ordered_map_definitions<S>(value: &HashMap<String, Vec<Definition>>, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, {
2514    let ordered: BTreeMap<_, Vec<_>> = value.iter().map(|(table_name, definitions)| (table_name, definitions.iter().map(Definition::stored).collect())).collect();
2515    ordered.serialize(serializer)
2516}
2517
2518/// Special serializer function for [`StoredField`] types, so the definitions of sequences are also stored without their processed fields.
2519///
2520/// The variant indexes must match the order of [`FieldType`]'s variants.
2521fn serialize_stored_field_type<S>(field_type: &&FieldType, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, {
2522    match field_type {
2523        FieldType::SequenceU16(definition) => serializer.serialize_newtype_variant("FieldType", 14, "SequenceU16", &definition.stored()),
2524        FieldType::SequenceU32(definition) => serializer.serialize_newtype_variant("FieldType", 15, "SequenceU32", &definition.stored()),
2525        FieldType::Boolean |
2526        FieldType::F32 |
2527        FieldType::F64 |
2528        FieldType::I16 |
2529        FieldType::I32 |
2530        FieldType::I64 |
2531        FieldType::ColourRGB |
2532        FieldType::StringU8 |
2533        FieldType::StringU16 |
2534        FieldType::OptionalI16 |
2535        FieldType::OptionalI32 |
2536        FieldType::OptionalI64 |
2537        FieldType::OptionalStringU8 |
2538        FieldType::OptionalStringU16 => field_type.serialize(serializer),
2539    }
2540}
2541
2542/// Special serializer function to sort the patches HashMap before serializing.
2543fn ordered_map_patches<S>(value: &HashMap<String, HashMap<String, HashMap<String, String>>>, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, {
2544    let ordered: BTreeMap<_, BTreeMap<_, BTreeMap<_, _>>> = value.iter().map(|(a, x)| (a, x.iter().map(|(b, y)| (b, y.iter().collect())).collect())).collect();
2545    ordered.serialize(serializer)
2546}
2547
2548#[cfg(test)]
2549mod schema_test;