Skip to main content

rpfm_lib/files/
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//! This module contains the definition of RFile, the file abstraction used by this lib to decode/encode files.
12//!
13//! There is an additional type: [`Unknown`]. This type is used as a wildcard,
14//! so you can get the raw data of any non-supported file type and manipulate it yourself in a safe way.
15//!
16//! For more information about specific file types, including their binary format spec, please
17//! **check their respective documentation**.
18//!
19//! # Core Abstractions
20//!
21//! ## RFile
22//!
23//! The [`RFile`] struct is the central abstraction for all files in rpfm_lib. It can represent:
24//! - Files within PackFiles (or any [`Container`])
25//! - Files on the filesystem
26//! - Files in memory only
27//!
28//! Key features:
29//! - **Lazy Loading**: Files can be loaded on-demand to reduce memory usage
30//! - **Type Detection**: Automatically identifies file types based on path and content
31//! - **Caching**: Supports caching decoded data or raw bytes
32//! - **Metadata**: Tracks path, timestamp, container name, and file type
33//!
34//! ## File States
35//!
36//! Files can be in one of three internal states:
37//! - **OnDisk**: Data not yet loaded (lazy loading)
38//! - **Cached**: Raw bytes loaded but not decoded
39//! - **Decoded**: Fully parsed into a type-specific structure
40//!
41//! ## Decoding/Encoding
42//!
43//! All file types implement the [`Decodeable`] and [`Encodeable`] traits:
44//! - **Decodeable**: Parse binary data into structured format
45//! - **Encodeable**: Serialize structured format back to binary
46//!
47//! Extra data can be passed via [`DecodeableExtraData`] and [`EncodeableExtraData`]
48//! to provide context like schemas, game info, or file names.
49//!
50//! # Known file types
51//!
52//! | File Type              | Decoding Supported | Encoding Supported |
53//! | ---------------------- | ------------------ | ------------------ |
54//! | [`Anim`]               | Limited            | Limited            |
55//! | [`AnimFragmentBattle`] | Limited            | Limited            |
56//! | [`AnimPack`]           | Yes                | Yes                |
57//! | [`AnimsTable`]         | Yes                | Yes                |
58//! | [`Atlas`]              | Yes                | Yes                |
59//! | [`Audio`]              | No                 | No                 |
60//! | [`BMD`]                | Limited            | Limited            |
61//! | [`BMD_Vegetation`]     | Limited            | Limited            |
62//! | [`CS2_Collision`]      | Limited            | Limited            |
63//! | [`CS2_Parsed`]         | Limited            | Limited            |
64//! | [`Dat`]                | Limited            | Limited            |
65//! | [`DB`]                 | Yes                | Yes                |
66//! | [`ESF`]                | Limited            | Limited            |
67//! | [`Font`]               | Limited            | Limited            |
68//! | [`GroupFormations`]    | Limited            | Limited            |
69//! | [`HLSL_Compiled`]      | Limited            | Limited            |
70//! | [`Image`]              | Limited            | Limited            |
71//! | [`Loc`]                | Yes                | Yes                |
72//! | [`MatchedCombat`]      | Yes                | Yes                |
73//! | [`Pack`]               | Yes                | Yes                |
74//! | [`PortraitSettings`]   | Yes                | Yes                |
75//! | [`RigidModel`]         | No                 | No                 |
76//! | [`SoundBank`]          | No                 | No                 |
77//! | SoundBankDatabase      | Limited            | Limited            |
78//! | SoundEvents            | Limited            | Limited            |
79//! | [`Text`]               | Yes                | Yes                |
80//! | [`TileDatabase`]       | Yes                | Yes                |
81//! | [`UIC`]                | No                 | No                 |
82//! | [`UnitVariant`]        | Yes                | Yes                |
83//! | [`Video`]              | Yes                | Yes                |
84//! | VMD                    | Yes                | Yes                |
85//! | WSModel                | Yes                | Yes                |
86//!
87//! # Example Usage
88//!
89//! ```ignore
90//! use rpfm_lib::files::{RFile, Decodeable, db::DB, DecodeableExtraData, table::Table};
91//! use rpfm_lib::schema::Schema;
92//! use std::path::Path;
93//!
94//! // Load a DB table from disk
95//! let schema = Schema::load(Path::new("path/to/schema.ron"), None).unwrap();
96//! let mut extra = DecodeableExtraData::default();
97//! extra.set_schema(Some(&schema));
98//! extra.set_table_name(Some("units_tables"));
99//!
100//! let rfile = RFile::from_disk(Path::new("units"), &extra).unwrap();
101//!
102//! // Access the decoded data
103//! if let Some(db) = rfile.decoded().and_then(|d| d.db()) {
104//!     println!("Table has {} rows", db.table().len());
105//! }
106//! ```
107//!
108//! [`Anim`]: crate::files::anim::Anim
109//! [`AnimFragmentBattle`]: crate::files::anim_fragment_battle::AnimFragmentBattle
110//! [`AnimPack`]: crate::files::animpack::AnimPack
111//! [`AnimsTable`]: crate::files::anims_table::AnimsTable
112//! [`Atlas`]: crate::files::atlas::Atlas
113//! [`Audio`]: crate::files::audio::Audio
114//! [`BMD`]: crate::files::bmd::Bmd
115//! [`BMD_Vegetation`]: crate::files::bmd_vegetation::BmdVegetation
116//! [`CS2_Collision`]: crate::files::cs2_collision::Cs2Collision
117//! [`CS2_Parsed`]: crate::files::cs2_parsed::Cs2Parsed
118//! [`Dat`]: crate::files::dat::Dat
119//! [`DB`]: crate::files::db::DB
120//! [`ESF`]: crate::files::esf::ESF
121//! [`Font`]: crate::files::font::Font
122//! [`GroupFormations`]: crate::files::group_formations::GroupFormations
123//! [`HLSL_Compiled`]: crate::files::hlsl_compiled::HlslCompiled
124//! [`Image`]: crate::files::image::Image
125//! [`Loc`]: crate::files::loc::Loc
126//! [`MatchedCombat`]: crate::files::matched_combat::MatchedCombat
127//! [`Pack`]: crate::files::pack::Pack
128//! [`PortraitSettings`]: crate::files::portrait_settings::PortraitSettings
129//! [`RigidModel`]: crate::files::rigidmodel::RigidModel
130//! [`SoundBank`]: crate::files::sound_bank::SoundBank
131//! [`SoundEvents`]: crate::files::sound_events::SoundEvents
132//! [`Text`]: crate::files::text::Text
133//! [`TileDatabase`]: crate::files::tile_database::TileDatabase
134//! [`UIC`]: crate::files::uic::UIC
135//! [`UnitVariant`]: crate::files::unit_variant::UnitVariant
136//! [`Unknown`]: crate::files::unknown::Unknown
137//! [`Video`]: crate::files::video::Video
138
139// File decoders intentionally use the `let mut x = T::default(); x.field = …;` pattern
140// to make debugging them easier.
141#![allow(clippy::field_reassign_with_default)]
142
143use crc_fast::{checksum, CrcAlgorithm};
144use csv::{QuoteStyle, ReaderBuilder, WriterBuilder};
145use getset::*;
146use log::warn;
147use rayon::prelude::*;
148use serde_derive::{Serialize, Deserialize};
149
150use std::cmp::Ordering;
151use std::collections::{HashMap, HashSet};
152use std::{fmt, fmt::{Debug, Display}};
153use std::fs::{DirBuilder, File};
154use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, BufWriter, Write};
155use std::path::{Path, PathBuf};
156
157use crate::binary::{ReadBytes, WriteBytes};
158use crate::compression::{CompressionFormat, Decompressible};
159use crate::encryption::Decryptable;
160use crate::error::{Result, RLibError};
161use crate::games::{GameInfo, pfh_version::PFHVersion, supported_games::*};
162use crate::{REGEX_CEO_DB, REGEX_DB, REGEX_PORTRAIT_SETTINGS};
163use crate::schema::{Schema, Definition};
164use crate::utils::*;
165
166use self::anim::Anim;
167use self::anim_fragment_battle::AnimFragmentBattle;
168use self::animpack::AnimPack;
169use self::anims_table::AnimsTable;
170use self::atlas::Atlas;
171use self::audio::Audio;
172use self::bmd::Bmd;
173use self::bmd_vegetation::BmdVegetation;
174use self::dat::Dat;
175use self::db::DB;
176use self::esf::ESF;
177use self::font::Font;
178use self::group_formations::GroupFormations;
179use self::hlsl_compiled::HlslCompiled;
180use self::image::Image;
181use self::loc::Loc;
182use self::matched_combat::MatchedCombat;
183use self::pack::{Pack, RESERVED_NAME_SETTINGS, RESERVED_NAME_NOTES, RESERVED_NAME_DEPENDENCIES_MANAGER, RESERVED_NAME_DEPENDENCIES_MANAGER_V2};
184use self::portrait_settings::PortraitSettings;
185use self::rigidmodel::RigidModel;
186use self::sound_bank::SoundBank;
187use self::text::Text;
188use self::uic::UIC;
189use self::unit_variant::UnitVariant;
190use self::unknown::Unknown;
191use self::video::Video;
192
193pub mod anim;
194pub mod anim_fragment_battle;
195pub mod animpack;
196pub mod anims_table;
197pub mod atlas;
198pub mod audio;
199#[allow(dead_code)]pub mod bmd;
200#[allow(dead_code)]pub mod bmd_vegetation;
201pub mod cs2_collision;
202pub mod cs2_parsed;
203pub mod dat;
204pub mod db;
205#[allow(dead_code)]pub mod esf;
206pub mod font;
207pub mod group_formations;
208pub mod hlsl_compiled;
209pub mod image;
210pub mod loc;
211pub mod matched_combat;
212pub mod pack;
213pub mod portrait_settings;
214pub mod rigidmodel;
215#[allow(dead_code)]pub mod sound_bank;
216pub mod sound_bank_database;
217pub mod sound_events;
218pub mod table;
219pub mod text;
220pub mod tile_database;
221pub mod uic;
222pub mod unit_variant;
223pub mod unknown;
224pub mod video;
225
226#[cfg(test)] mod rfile_test;
227
228//---------------------------------------------------------------------------//
229//                              Enum & Structs
230//---------------------------------------------------------------------------//
231
232/// Central file abstraction for rpfm_lib.
233///
234/// Represents a file that can exist in multiple locations and states:
235/// - Inside a PackFile or other container
236/// - On the filesystem
237/// - In memory only
238///
239/// # Lazy Loading
240///
241/// RFile supports lazy loading to minimize memory usage. Files can remain on disk
242/// until their data is actually needed, at which point they're loaded into memory
243/// automatically.
244///
245/// # File States
246///
247/// Internally, RFile can be in three states:
248/// - **OnDisk**: Metadata loaded, data still on disk (minimal memory)
249/// - **Cached**: Raw bytes loaded, not yet decoded
250/// - **Decoded**: Fully parsed into type-specific structure
251///
252/// # Type Detection
253///
254/// File types are detected based on:
255/// - File extension and path patterns
256/// - Magic numbers and header bytes
257/// - Container metadata
258///
259/// # Metadata
260///
261/// Each RFile tracks:
262/// - **path**: Location within container or filesystem (may be empty for memory-only files)
263/// - **timestamp**: Last modified time (optional)
264/// - **file_type**: Detected or specified file type
265/// - **container_name**: Source container name if from a container
266///
267/// # Example
268///
269/// ```ignore
270/// use rpfm_lib::files::{RFile, FileType};
271/// use std::path::Path;
272///
273/// // Load a file from disk
274/// let rfile = RFile::from_disk(Path::new("units.loc"), &None).unwrap();
275///
276/// // Check file type
277/// assert_eq!(*rfile.file_type(), FileType::Loc);
278///
279/// // Decode the file
280/// let decoded = rfile.decode(&None, false, false).unwrap();
281/// ```
282#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
283pub struct RFile {
284
285    /// Path of the file within a container or filesystem.
286    ///
287    /// Empty string for memory-only files.
288    path: String,
289
290    /// Last modified timestamp (Unix epoch).
291    timestamp: Option<u64>,
292
293    /// Detected or specified file type.
294    file_type: FileType,
295
296    /// Name of the source container, if applicable.
297    container_name: Option<String>,
298
299    /// Internal data storage (OnDisk, Cached, or Decoded).
300    ///
301    /// Use RFile methods instead of accessing directly.
302    data: RFileInnerData,
303}
304
305/// Internal data storage states for RFile.
306///
307/// Represents the three possible states of file data in memory:
308/// - **Decoded**: Fully parsed into structured format (highest memory, fastest access)
309/// - **Cached**: Raw bytes in memory (medium memory, requires decoding)
310/// - **OnDisk**: Metadata only, data on disk (lowest memory, requires loading + decoding)
311///
312/// This enum is internal and should not be used directly. Use RFile's public methods instead.
313///
314/// # State Transitions
315///
316/// ```text
317/// OnDisk → load() → Cached → decode() → Decoded
318///                     ↑                     ↓
319///                     └───── encode() ──────┘
320/// ```
321#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
322enum RFileInnerData {
323
324    /// File data loaded and decoded into type-specific structure.
325    ///
326    /// Ready for immediate use. Highest memory usage.
327    Decoded(Box<RFileDecoded>),
328
329    /// Raw bytes loaded into memory but not decoded.
330    ///
331    /// Requires decoding before use. Medium memory usage.
332    Cached(Vec<u8>),
333
334    /// File data still on disk, only metadata in memory.
335    ///
336    /// Requires loading and decoding before use. Lowest memory usage.
337    OnDisk(OnDisk)
338}
339
340/// This struct represents a file on disk, which data has not been loaded to memory yet.
341///
342/// This may be a file directly on disk, or one inside another file (like inside a [Container]).
343///
344/// This is internal only. Users should not use it directly.
345#[derive(Clone, Debug, PartialEq, Getters, Serialize, Deserialize)]
346struct OnDisk {
347
348    /// Path of the file on disk where the data is.
349    ///
350    /// This may be a singular file or a file containing it
351    path: String,
352
353    /// Last modified date of the file that contains the data.
354    ///
355    /// This is used to both, get the last modified data into the file's metadata
356    /// and to check if the file has been manipulated since we created the OnDisk of it.
357    timestamp: u64,
358
359    /// Offset of the start of the file's data.
360    ///
361    /// `0` if the whole file is the data we want.
362    start: u64,
363
364    /// Size in bytes of the file's data.
365    size: u64,
366
367    /// Is the data compressed?.
368    is_compressed: bool,
369
370    /// Is the data encrypted? And if so, with which format?.
371    is_encrypted: Option<PFHVersion>,
372}
373
374/// This enum allow us to store any kind of decoded file type on a common place.
375#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
376pub enum RFileDecoded {
377    Anim(Anim),
378    AnimFragmentBattle(AnimFragmentBattle),
379    AnimPack(AnimPack),
380    AnimsTable(AnimsTable),
381    Atlas(Atlas),
382    Audio(Audio),
383    BMD(Box<Bmd>),
384    BMDVegetation(BmdVegetation),
385    Dat(Dat),
386    DB(DB),
387    ESF(ESF),
388    Font(Font),
389    GroupFormations(GroupFormations),
390    HlslCompiled(HlslCompiled),
391    Image(Image),
392    Loc(Loc),
393    MatchedCombat(MatchedCombat),
394    Pack(Pack),
395    PortraitSettings(PortraitSettings),
396    RigidModel(RigidModel),
397    SoundBank(SoundBank),
398    Text(Text),
399    UIC(UIC),
400    UnitVariant(UnitVariant),
401    Unknown(Unknown),
402    Video(Video),
403    VMD(Text),
404    WSModel(Text),
405}
406
407/// Known file types in Total War games.
408///
409/// Categorizes files by their format and purpose. Each variant corresponds to a dedicated
410/// submodule that implements parsing and encoding for that file type.
411///
412/// # Type Detection
413///
414/// File types are determined by:
415/// - **Extension matching**: Primary method for most file types
416/// - **Path patterns**: For files with special naming (e.g., DB tables)
417/// - **Magic numbers**: For format disambiguation when needed
418///
419/// # Support Levels
420///
421/// - **Full**: Complete decoding and encoding support
422/// - **Partial**: Read support with limitations
423/// - **Passthrough**: Raw data only (use [`Unknown`] for custom handling)
424///
425/// See the module-level documentation for a complete support matrix.
426///
427/// # Unknown Type
428///
429/// [`FileType::Unknown`] is the default fallback for unrecognized files. These files
430/// can still be read and written using the [`Unknown`] file type, which provides
431/// access to raw bytes.
432#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
433pub enum FileType {
434    Anim,
435    AnimFragmentBattle,
436    AnimPack,
437    AnimsTable,
438    Atlas,
439    Audio,
440    BMD,
441    BMDVegetation,
442    Dat,
443    DB,
444    ESF,
445    Font,
446    GroupFormations,
447    HlslCompiled,
448    Image,
449    Loc,
450    MatchedCombat,
451    Pack,
452    PortraitSettings,
453    RigidModel,
454    SoundBank,
455    Text,
456    UIC,
457    UnitVariant,
458    Video,
459    VMD,
460    WSModel,
461
462    #[default]
463    Unknown,
464}
465
466/// This enum represents a ***Path*** inside a [Container].
467#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
468pub enum ContainerPath {
469
470    /// This variant represents the path of a single file.
471    File(String),
472
473    /// This variant represents the path of a single folder.
474    ///
475    /// If this is empty, it represents the root of the container.
476    Folder(String),
477}
478
479/// Additional context data for [`Decodeable::decode()`] operations.
480///
481/// This structure provides optional configuration and metadata that decoders may need
482/// to properly interpret binary data. Different file types use different subsets of
483/// these fields.
484///
485/// # Field Categories
486///
487/// - **Configuration toggles**: Control decoder behavior (lazy loading, encryption status, etc.)
488/// - **OnDisk-related data**: File location and timestamp information
489/// - **Table-related data**: Database table-specific context
490/// - **Image-related data**: Image format detection flags
491/// - **General-purpose data**: Game info, file names, sizes
492///
493/// # Usage
494///
495/// ```ignore
496/// use rpfm_lib::files::DecodeableExtraData;
497///
498/// // For decoding a DB table
499/// let extra_data = DecodeableExtraData::default()
500///     .set_schema(Some(&schema))
501///     .set_game_info(Some(&game_info))
502///     .set_table_name(Some("units_tables"))
503///     .set_return_incomplete(true);
504/// ```
505///
506/// # Required Fields by Type
507///
508/// - **DB Tables**: Require `schema` and optionally `table_name` for fragments
509/// - **Containers (PackFiles)**: Use `lazy_load`, `disk_file_path`, `disk_file_offset`
510/// - **Images**: Use `is_dds` to enable DDS-specific decoding
511/// - **Generic files**: May use `game_info`, `file_name`, `data_size`
512///
513/// For specific requirements, consult each file type's documentation.
514#[derive(Clone, Debug, Default, Getters, Setters)]
515#[getset(get = "pub", set = "pub")]
516pub struct DecodeableExtraData<'a> {
517
518    //-----------------------//
519    // Configuration toggles //
520    //-----------------------//
521
522    /// Enable lazy loading for container files.
523    ///
524    /// When `true`, [`Container`] implementors will defer loading file data until accessed,
525    /// storing only metadata initially. This reduces memory usage for large containers.
526    lazy_load: bool,
527
528    /// Indicates whether the source data was encrypted.
529    ///
530    /// This is informational only - data reaching decode functions should already be decrypted.
531    /// Used for metadata tracking and logging.
532    is_encrypted: bool,
533
534    /// Allow returning partial data on decode errors (DB tables only).
535    ///
536    /// When `true`, table decoders will return successfully decoded rows even if later
537    /// rows fail to decode. When `false`, any decode error fails the entire operation.
538    return_incomplete: bool,
539
540    /// Schema definition for decoding DB tables and Loc files.
541    ///
542    /// Required for decoding database tables, as the schema defines the table structure,
543    /// column types, and versioning information.
544    schema: Option<&'a Schema>,
545
546    //----------------------------//
547    // OnDisk-related config data //
548    //----------------------------//
549
550    /// Absolute path to the file on disk.
551    ///
552    /// Used by lazy-loading containers to know where to read data from when needed.
553    /// Should be `None` for in-memory data.
554    disk_file_path: Option<&'a str>,
555
556    /// Byte offset within the disk file where this file's data begins.
557    ///
558    /// Used for files embedded within larger containers (e.g., individual files in a PackFile).
559    /// Set to `0` for standalone files.
560    disk_file_offset: u64,
561
562    /// File modification timestamp (Unix epoch seconds).
563    ///
564    /// Preserved from the original file for metadata tracking.
565    timestamp: u64,
566
567    //----------------------------//
568    // Table-related config data  //
569    //----------------------------//
570
571    /// Name of the table (without extension or path).
572    ///
573    /// Used when decoding table fragments that don't have the full path context.
574    /// For example, when decoding a table from a loose file or unnamed buffer.
575    table_name: Option<&'a str>,
576
577    //----------------------------//
578    // Image-Related config data  //
579    //----------------------------//
580
581    /// Flag indicating the image is in DDS (DirectDraw Surface) format.
582    ///
583    /// When `true`, enables DDS-specific decoding and conversion to PNG for display.
584    is_dds: bool,
585
586    //------------------------------//
587    // General-purpose config data //
588    //------------------------------//
589
590    /// Complete game information context.
591    ///
592    /// Provides game-specific settings, version info, and feature flags that may
593    /// affect decoding behavior (e.g., format variations between game versions).
594    game_info: Option<&'a GameInfo>,
595
596    /// Original filename (with extension).
597    ///
598    /// Used for file type detection, logging, and error messages.
599    file_name: Option<&'a str>,
600
601    /// Total size of the file data in bytes.
602    ///
603    /// May differ from buffer size if dealing with partial data or compressed streams.
604    data_size: u64,
605
606    /// Skip automatic path cache generation for containers.
607    ///
608    /// When `true`, container decoders won't build the lowercase path cache automatically.
609    /// You must manually call [`Container::paths_cache_generate()`] after decoding or
610    /// case-insensitive lookups will fail.
611    ///
612    /// This is an optimization for bulk operations where you'll rebuild the cache once
613    /// at the end instead of incrementally.
614    skip_path_cache_generation: bool,
615}
616
617/// Additional context data for [`Encodeable::encode()`] operations.
618///
619/// This structure provides optional configuration and metadata that encoders may need
620/// to properly serialize structured data to binary format. Different file types use
621/// different subsets of these fields.
622///
623/// # Field Categories
624///
625/// - **Configuration toggles**: Control encoder behavior (test mode, date handling, GUIDs)
626/// - **Optional config data**: Game info, compression settings
627///
628/// # Usage
629///
630/// ```ignore
631/// use rpfm_lib::files::EncodeableExtraData;
632/// use rpfm_lib::compression::CompressionFormat;
633///
634/// // For encoding a DB table with GUID
635/// let extra_data = EncodeableExtraData::default()
636///     .set_game_info(Some(&game_info))
637///     .set_table_has_guid(true)
638///     .set_regenerate_table_guid(true);
639///
640/// // For encoding with compression
641/// let extra_data = EncodeableExtraData::default()
642///     .set_compression_format(CompressionFormat::Lz4)
643///     .set_game_info(Some(&game_info));
644/// ```
645///
646/// # Common Configurations
647///
648/// - **DB Tables**: Use `table_has_guid` and `regenerate_table_guid` to control GUID handling
649/// - **Containers (PackFiles)**: Use `compression_format` to enable compression
650/// - **Testing**: Use `test_mode` and `nullify_dates` for deterministic output
651/// - **ESF Files**: Use `disable_compression` for nested encoding
652///
653/// For specific requirements, consult each file type's documentation.
654#[derive(Clone, Default, Getters, Setters)]
655#[getset(get = "pub", set = "pub")]
656pub struct EncodeableExtraData<'a> {
657
658    //-----------------------//
659    // Configuration toggles //
660    //-----------------------//
661
662    /// Enable test mode for deterministic output.
663    ///
664    /// When `true`, encoders may skip randomization or use fixed values for fields
665    /// that would normally vary (like auto-generated IDs), making output reproducible
666    /// for testing purposes.
667    test_mode: bool,
668
669    /// Zero out all date and timestamp fields.
670    ///
671    /// When `true`, any date or timestamp fields are written as `0` instead of their
672    /// actual values. Used in conjunction with `test_mode` for reproducible output,
673    /// or when dates should be reset.
674    nullify_dates: bool,
675
676    /// Include a GUID in the DB table header.
677    ///
678    /// When `true`, table encoders will write a GUID (Globally Unique Identifier) in
679    /// the table header. Required for Shogun 2 and newer games. Must be `false` for Empire
680    /// and Napoleon, as including a GUID crashes those games on load.
681    table_has_guid: bool,
682
683    /// Generate a new GUID for the table instead of preserving the existing one.
684    ///
685    /// When `true`, a fresh random GUID is generated. When `false`, the existing GUID
686    /// (if any) is preserved. Only meaningful when `table_has_guid` is also `true`.
687    regenerate_table_guid: bool,
688
689    //-----------------------//
690    // Optional config data  //
691    //-----------------------//
692
693    /// Complete game information context.
694    ///
695    /// Provides game-specific settings, version info, and feature flags that may
696    /// affect encoding behavior (e.g., format variations between game versions).
697    game_info: Option<&'a GameInfo>,
698
699    /// Compression format to use when writing files.
700    ///
701    /// Specifies which compression algorithm to apply. Common formats include:
702    /// - [`CompressionFormat::None`]: No compression
703    /// - [`CompressionFormat::Lz4`]: Fast compression
704    /// - [`CompressionFormat::Zstd`]: Modern compression (best ratio)
705    /// - [`CompressionFormat::Lzma1`]: Legacy compression (older games)
706    ///
707    /// The game info may override this based on what the target game supports.
708    compression_format: CompressionFormat,
709
710    /// Disable compression for nested encoding operations.
711    ///
712    /// When `true`, prevents compression even if `compression_format` is set. Used for
713    /// ESF (Empire Save File) encoding where the outer container handles compression
714    /// and inner structures should remain uncompressed to avoid double-compression.
715    disable_compression: bool
716}
717
718//---------------------------------------------------------------------------//
719//                           Trait Definitions
720//---------------------------------------------------------------------------//
721
722/// Generic trait for decoding binary data into structured types.
723///
724/// This trait provides a standardized interface for deserializing binary data from any
725/// source implementing [`ReadBytes`]. All Total War file types
726/// in RPFM implement this trait to enable consistent decoding behavior.
727///
728/// # Type Parameters
729///
730/// The trait is object-safe and requires `Send + Sync` to enable concurrent decoding operations.
731///
732/// # Examples
733///
734/// ```ignore
735/// use rpfm_lib::files::{Decodeable, DecodeableExtraData};
736/// use rpfm_lib::binary::ReadBytes;
737///
738/// let data = &[0x01, 0x02, 0x03, 0x04];
739/// let extra_data = None;
740/// let decoded = MyType::decode(&mut data.as_slice(), &extra_data)?;
741/// ```
742pub trait Decodeable: Send + Sync {
743
744    /// Decodes binary data into the implementing type.
745    ///
746    /// This method reads from any source implementing [`ReadBytes`]
747    /// and constructs an instance of the implementing type.
748    ///
749    /// # Parameters
750    ///
751    /// - `data`: A mutable reference to a type implementing [`ReadBytes`]
752    /// - `extra_data`: Optional additional context needed for decoding (schemas, game version, etc.)
753    ///
754    /// # Returns
755    ///
756    /// Returns `Ok(Self)` on successful decoding, or an error if the data is malformed or
757    /// insufficient context was provided.
758    ///
759    /// # Errors
760    ///
761    /// This function may return errors if:
762    /// - The binary data is corrupted or malformed
763    /// - Required schema information is missing from `extra_data`
764    /// - The data stream ends unexpectedly
765    fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> where Self: Sized;
766}
767
768/// Generic trait for encoding structured types into binary data.
769///
770/// This trait provides a standardized interface for serializing structured data to any
771/// destination implementing [`WriteBytes`]. All Total War file types
772/// in RPFM implement this trait to enable consistent encoding behavior.
773///
774/// # Type Parameters
775///
776/// The trait is object-safe and requires `Send + Sync` to enable concurrent encoding operations.
777///
778/// # Examples
779///
780/// ```ignore
781/// use rpfm_lib::files::{Encodeable, EncodeableExtraData};
782/// use rpfm_lib::binary::WriteBytes;
783///
784/// let mut buffer = Vec::new();
785/// let extra_data = None;
786/// my_instance.encode(&mut buffer, &extra_data)?;
787/// ```
788pub trait Encodeable: Send + Sync {
789
790    /// Encodes the implementing type into binary data.
791    ///
792    /// This method writes to any destination implementing [`WriteBytes`],
793    /// serializing the instance's data in the appropriate Total War file format.
794    ///
795    /// # Parameters
796    ///
797    /// - `buffer`: A mutable reference to a type implementing [`WriteBytes`]
798    /// - `extra_data`: Optional additional context needed for encoding (schemas, game version, etc.)
799    ///
800    /// # Returns
801    ///
802    /// Returns `Ok(())` on successful encoding, or an error if the encoding process fails.
803    ///
804    /// # Errors
805    ///
806    /// This function may return errors if:
807    /// - Writing to the buffer fails
808    /// - Required schema information is missing from `extra_data`
809    /// - The data contains invalid values that cannot be serialized
810    fn encode<W: WriteBytes>(&mut self, buffer: &mut W, extra_data: &Option<EncodeableExtraData>) -> Result<()>;
811}
812
813/// Interface for working with container-like files.
814///
815/// This trait provides a unified API for manipulating file containers such as PackFiles,
816/// allowing implementors to store, retrieve, and manage collections of [`RFile`]s with
817/// hierarchical path structures.
818///
819/// # Implementors
820///
821/// - `Pack`: Total War PackFile containers (`.pack` files)
822/// - Other container formats that store multiple files
823///
824/// # Core Operations
825///
826/// The trait provides several categories of operations:
827///
828/// - **File access**: Get references to files by path, type, or pattern
829/// - **Insertion**: Add files from disk or [`RFile`] instances
830/// - **Extraction**: Write files to disk, optionally as TSV for DB/Loc files
831/// - **Removal**: Delete files or folders by path
832/// - **Queries**: Check existence, list folders, filter by type
833///
834/// # Path Handling
835///
836/// All paths use forward slashes (`/`) as separators, regardless of OS. Paths starting
837/// with `/` are automatically normalized by removing the leading slash.
838pub trait Container {
839
840    /// Extracts files from the container to disk.
841    ///
842    /// This method writes files matching the provided [`ContainerPath`] to the filesystem,
843    /// optionally preserving the container's folder structure.
844    ///
845    /// # Parameters
846    ///
847    /// - `container_path`: Path to file or folder within the container to extract
848    /// - `destination_path`: Target directory on disk where files will be written
849    /// - `keep_container_path_structure`: If `true`, preserves the container's folder hierarchy
850    /// - `schema`: If provided, attempts to export DB/Loc files as TSV (falls back to binary on error)
851    /// - `case_insensitive`: Enable case-insensitive folder matching (file extraction is always case-sensitive)
852    /// - `keys_first`: When exporting to TSV, place key columns first
853    /// - `extra_data`: Optional encoding context for binary files
854    ///
855    /// # Returns
856    ///
857    /// Returns a list of paths to the extracted files on disk.
858    ///
859    /// # Errors
860    ///
861    /// Returns an error if:
862    /// - The specified container path doesn't exist
863    /// - Disk I/O operations fail
864    /// - File decoding fails (for TSV export)
865    ///
866    /// # Examples
867    ///
868    /// ```ignore
869    /// // Extract a single file, preserving structure
870    /// let paths = container.extract(
871    ///     ContainerPath::File("db/units_tables/units.bin".to_string()),
872    ///     Path::new("./output"),
873    ///     true,  // Keep structure
874    ///     &Some(schema),
875    ///     false, // Case-sensitive
876    ///     true,  // Keys first
877    ///     &None,
878    /// )?;
879    ///
880    /// // Extract entire folder as TSV
881    /// let paths = container.extract(
882    ///     ContainerPath::Folder("db/".to_string()),
883    ///     Path::new("./output"),
884    ///     false, // Flat extraction
885    ///     &Some(schema),
886    ///     true,  // Case-insensitive
887    ///     true,
888    ///     &None,
889    /// )?;
890    /// ```
891    #[allow(clippy::too_many_arguments)]
892    fn extract(&mut self,
893        container_path: ContainerPath,
894        destination_path: &Path,
895        keep_container_path_structure: bool,
896        schema: &Option<Schema>,
897        case_insensitive: bool,
898        keys_first: bool,
899        extra_data: &Option<EncodeableExtraData>,
900    ) -> Result<Vec<PathBuf>> {
901
902        let mut extracted_paths = vec![];
903        match container_path {
904            ContainerPath::File(mut container_path) => {
905                if container_path.starts_with('/') {
906                    container_path.remove(0);
907                }
908
909                let destination_path = if keep_container_path_structure {
910                    destination_path.to_owned().join(&container_path)
911                } else {
912                    destination_path.to_owned()
913                };
914
915                let mut destination_folder = destination_path.to_owned();
916                destination_folder.pop();
917                DirBuilder::new().recursive(true).create(&destination_folder)?;
918
919                let rfile = self.files_mut().get_mut(&container_path).ok_or_else(|| RLibError::FileNotFound(container_path.to_string()))?;
920
921                // If we want to extract as tsv and we got a db/loc, export to tsv.
922                if let Some(schema) = schema {
923                    if rfile.file_type() == FileType::DB || rfile.file_type() == FileType::Loc {
924                        let mut destination_path_tsv = destination_path.to_owned();
925
926                        // Make sure to NOT replace the extension if there is one, only append to it.
927                        match destination_path_tsv.extension() {
928                            Some(extension) => {
929                                let extension = format!("{}.tsv", extension.to_string_lossy());
930                                destination_path_tsv.set_extension(extension)
931                            },
932                            None => destination_path_tsv.set_extension("tsv"),
933                        };
934
935                        let result = rfile.tsv_export_to_path(&destination_path_tsv, schema, keys_first);
936
937                        // If it fails to extract as tsv, extract as binary.
938                        if result.is_err() {
939                            warn!("File with path {} failed to extract as TSV. Extracting it as binary.", rfile.path_in_container_raw());
940
941                            let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
942                            extracted_paths.push(extracted_path);
943                        } else {
944                            extracted_paths.push(destination_path_tsv);
945                            result?;
946                        }
947                    } else {
948                        let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
949                        extracted_paths.push(extracted_path);
950                    }
951                }
952
953                // Otherwise, just write the binary data to disk.
954                else {
955                    let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
956                    extracted_paths.push(extracted_path);
957                }
958            }
959            ContainerPath::Folder(mut container_path) => {
960                if container_path.starts_with('/') {
961                    container_path.remove(0);
962                }
963
964                let mut rfiles = self.files_by_path_mut(&ContainerPath::Folder(container_path.clone()), case_insensitive);
965                for rfile in &mut rfiles {
966                    let container_path = rfile.path_in_container_raw();
967                    let destination_path = if keep_container_path_structure {
968                        destination_path.to_owned().join(container_path)
969                    } else {
970                        destination_path.to_owned()
971                    };
972
973                    let mut destination_folder = destination_path.to_owned();
974                    destination_folder.pop();
975                    DirBuilder::new().recursive(true).create(&destination_folder)?;
976
977                    // If we want to extract as tsv and we got a db/loc, export to tsv.
978                    if let Some(schema) = schema {
979                        if rfile.file_type() == FileType::DB || rfile.file_type() == FileType::Loc {
980                            let mut destination_path_tsv = destination_path.to_owned();
981
982                            // Make sure to NOT replace the extension if there is one, only append to it.
983                            match destination_path_tsv.extension() {
984                                Some(extension) => {
985                                    let extension = format!("{}.tsv", extension.to_string_lossy());
986                                    destination_path_tsv.set_extension(extension)
987                                },
988                                None => destination_path_tsv.set_extension("tsv"),
989                            };
990
991                            let result = rfile.tsv_export_to_path(&destination_path_tsv, schema, keys_first);
992
993                            // If it fails to extract as tsv, extract as binary.
994                            if result.is_err() {
995                                warn!("File with path {} failed to extract as TSV. Extracting it as binary.", rfile.path_in_container_raw());
996
997                                let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
998                                extracted_paths.push(extracted_path);
999                            } else {
1000                                extracted_paths.push(destination_path_tsv);
1001                                result?;
1002                            }
1003                        } else {
1004                            let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
1005                            extracted_paths.push(extracted_path);
1006                        }
1007                    }
1008
1009                    // Otherwise, just write the binary data to disk.
1010                    else {
1011                        let extracted_path = rfile.sanitize_and_create_file(&destination_path, extra_data)?;
1012                        extracted_paths.push(extracted_path);
1013                    }
1014
1015                }
1016
1017                // If we're extracting the whole container, also extract any relevant metadata file associated with it.
1018                if container_path.is_empty() {
1019                    extracted_paths.append(&mut self.extract_metadata(destination_path)?);
1020                }
1021            }
1022        }
1023
1024        Ok(extracted_paths)
1025    }
1026
1027    /// Extracts container metadata as `.json` files.
1028    ///
1029    /// This method writes any metadata associated with the container (such as pack settings,
1030    /// notes, or configuration) to the specified destination directory.
1031    ///
1032    /// # Parameters
1033    ///
1034    /// - `destination_path`: Directory where metadata files will be written
1035    ///
1036    /// # Returns
1037    ///
1038    /// Returns a list of paths to the extracted metadata files.
1039    ///
1040    /// # Default Implementation
1041    ///
1042    /// The default implementation does nothing and returns an empty list. Container types
1043    /// with metadata should override this method.
1044    fn extract_metadata(&mut self, _destination_path: &Path) -> Result<Vec<PathBuf>> {
1045        Ok(vec![])
1046    }
1047
1048    /// Inserts an [`RFile`] into the container.
1049    ///
1050    /// If a file with the same path already exists, it will be replaced.
1051    ///
1052    /// # Parameters
1053    ///
1054    /// - `file`: The [`RFile`] to insert
1055    ///
1056    /// # Returns
1057    ///
1058    /// Returns the [`ContainerPath`] of the inserted file, or `None` if insertion failed.
1059    fn insert(&mut self, file: RFile) -> Result<Option<ContainerPath>> {
1060        let path = file.path_in_container();
1061        let path_raw = file.path_in_container_raw();
1062
1063        self.paths_cache_insert_path(path_raw);
1064        self.files_mut().insert(path_raw.to_owned(), file);
1065        Ok(Some(path))
1066    }
1067
1068    /// Inserts a file from disk into the container.
1069    ///
1070    /// This method reads a file from the filesystem and inserts it at the specified path
1071    /// within the container. If a file already exists at that path, it will be replaced.
1072    ///
1073    /// # TSV Import
1074    ///
1075    /// If a [`Schema`] is provided and the source file has a `.tsv` extension, this method
1076    /// will attempt to import it as a binary DB/Loc file. If the conversion fails, it falls
1077    /// back to importing it as a plain text file.
1078    ///
1079    /// # Parameters
1080    ///
1081    /// - `source_path`: Path to the file on disk to import
1082    /// - `container_path_folder`: Target path within the container (folder or full path)
1083    /// - `schema`: Optional schema for TSV-to-binary conversion
1084    ///
1085    /// # Returns
1086    ///
1087    /// Returns the [`ContainerPath`] of the inserted file, or `None` if insertion failed.
1088    ///
1089    /// # Errors
1090    ///
1091    /// Returns an error if:
1092    /// - The source file cannot be read
1093    /// - File type detection fails
1094    ///
1095    /// # Path Behavior
1096    ///
1097    /// - If `container_path_folder` ends with `/` or is empty, the source filename is appended
1098    /// - Otherwise, `container_path_folder` is used as the full target path
1099    fn insert_file(&mut self, source_path: &Path, container_path_folder: &str, schema: &Option<Schema>) -> Result<Option<ContainerPath>> {
1100        let mut container_path_folder = container_path_folder.replace('\\', "/");
1101        if container_path_folder.starts_with('/') {
1102            container_path_folder.remove(0);
1103        }
1104
1105        if container_path_folder.ends_with('/') || container_path_folder.is_empty() {
1106            let trimmed_path = source_path.file_name()
1107                .ok_or_else(|| RLibError::PathMissingFileName(source_path.to_string_lossy().to_string()))?
1108                .to_string_lossy().to_string();
1109            container_path_folder = container_path_folder.to_owned() + &trimmed_path;
1110        }
1111
1112        // If tsv import is enabled, try to import the file to binary before adding it to the Container.
1113        let mut tsv_imported = false;
1114        let mut rfile = match source_path.extension() {
1115            Some(extension) if extension.to_string_lossy() == "tsv" => {
1116                tsv_imported = true;
1117                let rfile = RFile::tsv_import_from_path(source_path, schema);
1118                if let Err(error) = rfile {
1119                    warn!("File with path {} failed to import as TSV. Importing it as binary. If you're using the CLI - did you forget to provide schema with --tsv-as-binary flag? Error was: {}", &source_path.to_string_lossy(), error);
1120
1121                    tsv_imported = false;
1122                    RFile::new_from_file_path(source_path)
1123                } else {
1124                    rfile
1125                }
1126            }
1127            _ => RFile::new_from_file_path(source_path),
1128        }?;
1129
1130        if !tsv_imported {
1131            rfile.set_path_in_container_raw(&container_path_folder);
1132        }
1133
1134        // Make sure to guess the file type before inserting it.
1135        rfile.load()?;
1136        rfile.guess_file_type()?;
1137
1138        self.insert(rfile)
1139    }
1140
1141    /// Inserts an entire folder from disk into the container recursively.
1142    ///
1143    /// This method recursively scans a directory and imports all files, preserving
1144    /// the folder structure. Files with identical paths in the container are replaced.
1145    ///
1146    /// # TSV Import
1147    ///
1148    /// If a [`Schema`] is provided, `.tsv` files will be converted to binary DB/Loc format.
1149    /// If conversion fails, they're imported as plain text files.
1150    ///
1151    /// # Parameters
1152    ///
1153    /// - `source_path`: Path to the folder on disk to import
1154    /// - `container_path_folder`: Target folder path within the container
1155    /// - `ignored_paths`: Optional list of relative paths to exclude from import
1156    /// - `schema`: Optional schema for TSV-to-binary conversion
1157    /// - `include_base_folder`: If `true`, includes the source folder name in container paths
1158    ///
1159    /// # Returns
1160    ///
1161    /// Returns a list of all [`ContainerPath`]s that were inserted.
1162    ///
1163    /// # Errors
1164    ///
1165    /// Returns an error if:
1166    /// - The source directory cannot be read
1167    /// - Any file read or type detection fails
1168    ///
1169    /// # Folder Inclusion Behavior
1170    ///
1171    /// - `include_base_folder = false`: Contents of `source_path` go directly into `container_path_folder`
1172    /// - `include_base_folder = true`: A subfolder with the source folder's name is created first
1173    ///
1174    /// # Examples
1175    ///
1176    /// ```ignore
1177    /// // Import folder contents directly into "data/"
1178    /// container.insert_folder(
1179    ///     Path::new("./my_mod"),
1180    ///     "data/",
1181    ///     &None,
1182    ///     &Some(schema),
1183    ///     false  // Don't include "my_mod" folder name
1184    /// )?;
1185    ///
1186    /// // Import with ignored paths
1187    /// container.insert_folder(
1188    ///     Path::new("./my_mod"),
1189    ///     "data/",
1190    ///     &Some(vec![".git/", "node_modules/"]),
1191    ///     &None,
1192    ///     true  // Include "my_mod" folder name
1193    /// )?;
1194    /// ```
1195    fn insert_folder(&mut self, source_path: &Path, container_path_folder: &str, ignored_paths: &Option<Vec<&str>>, schema: &Option<Schema>, include_base_folder: bool) -> Result<Vec<ContainerPath>> {
1196        let mut container_path_folder = container_path_folder.replace('\\', "/");
1197        if !container_path_folder.is_empty() && !container_path_folder.ends_with('/') {
1198            container_path_folder.push('/');
1199        }
1200
1201        if container_path_folder.starts_with('/') {
1202            container_path_folder.remove(0);
1203        }
1204
1205        let mut source_path_without_base_folder = source_path.to_path_buf();
1206        source_path_without_base_folder.pop();
1207
1208        let file_paths = files_from_subdir(source_path, true)?;
1209        let mut inserted_paths = Vec::with_capacity(file_paths.len());
1210        for file_path in file_paths {
1211            let trimmed_path = if include_base_folder {
1212                file_path.strip_prefix(&source_path_without_base_folder)?
1213            } else {
1214                file_path.strip_prefix(source_path)?
1215            }.to_string_lossy().replace('\\', "/");
1216
1217            let file_container_path = container_path_folder.to_owned() + &trimmed_path;
1218
1219            if let Some(ignored_paths) = ignored_paths {
1220                if ignored_paths.iter().any(|x| trimmed_path.starts_with(x)) {
1221                    continue;
1222                }
1223            }
1224
1225            // If tsv import is enabled, try to import the file to binary before adding it to the Container.
1226            let mut tsv_imported = false;
1227            let mut rfile = match file_path.extension() {
1228                Some(extension) if extension.to_string_lossy() == "tsv" => {
1229                    tsv_imported = true;
1230                    let rfile = RFile::tsv_import_from_path(&file_path, schema);
1231                    if let Err(error) = rfile {
1232                        warn!("File with path {} failed to import as TSV. Importing it as binary. If you're using the CLI - did you forget to provide schema with --tsv-as-binary flag? Error was: {}", &file_path.to_string_lossy(), error);
1233
1234                        tsv_imported = false;
1235                        RFile::new_from_file_path(&file_path)
1236                    } else {
1237                        rfile
1238                    }
1239                }
1240                _ => RFile::new_from_file_path(&file_path),
1241            }?;
1242
1243            if !tsv_imported {
1244                rfile.set_path_in_container_raw(&file_container_path);
1245            }
1246
1247            // Make sure to guess the file type before inserting it.
1248            rfile.load()?;
1249            rfile.guess_file_type()?;
1250
1251            if let Some(path) = self.insert(rfile)? {
1252                inserted_paths.push(path);
1253            }
1254        }
1255
1256        Ok(inserted_paths)
1257    }
1258
1259    /// Removes files matching the provided [`ContainerPath`] from the container.
1260    ///
1261    /// This method deletes files or entire folder hierarchies from the container.
1262    ///
1263    /// # Parameters
1264    ///
1265    /// - `path`: The container path to remove (file or folder)
1266    ///
1267    /// # Returns
1268    ///
1269    /// Returns a list of removed [`ContainerPath`]s, always using the [`File`](ContainerPath::File) variant.
1270    ///
1271    /// # Special Cases
1272    ///
1273    /// - `ContainerPath::Folder("")`: Represents the container root, deletes **all** files
1274    /// - `ContainerPath::File(...)`: Deletes a single file
1275    /// - `ContainerPath::Folder(...)`: Deletes all files under that folder (recursive)
1276    ///
1277    /// # Examples
1278    ///
1279    /// ```ignore
1280    /// // Remove a single file
1281    /// container.remove(&ContainerPath::File("data/units.bin".to_string()));
1282    ///
1283    /// // Remove entire folder
1284    /// container.remove(&ContainerPath::Folder("db/".to_string()));
1285    ///
1286    /// // Clear entire container
1287    /// container.remove(&ContainerPath::Folder("".to_string()));
1288    /// ```
1289    fn remove(&mut self, path: &ContainerPath) -> Vec<ContainerPath> {
1290        match path {
1291            ContainerPath::File(path) => {
1292                let mut path = path.to_owned();
1293                if path.starts_with('/') {
1294                    path.remove(0);
1295                }
1296
1297                self.paths_cache_remove_path(&path);
1298                self.files_mut().remove(&path);
1299                vec![ContainerPath::File(path.to_owned())]
1300            },
1301            ContainerPath::Folder(path) => {
1302                let mut path = path.to_owned();
1303                if path.starts_with('/') {
1304                    path.remove(0);
1305                }
1306
1307                // If the path is empty, we mean the root of the container, including everything on it.
1308                if path.is_empty() {
1309                    self.files_mut().clear();
1310                    vec![ContainerPath::Folder(String::new()); 1]
1311                }
1312
1313                // Otherwise, it's a normal folder.
1314                else {
1315                    let mut path_full = path.to_owned();
1316                    path_full.push('/');
1317
1318                    let paths_to_remove = self.files().par_iter()
1319                        .filter_map(|(key, _)| {
1320
1321                            // Make sure to only pick folders, not files matching folder names or partial folder matches!
1322                            if key.starts_with(&path_full) {
1323                                Some(key.to_owned())
1324                            } else {
1325                                None
1326                            }
1327                        }).collect::<Vec<String>>();
1328
1329                    paths_to_remove.iter().for_each(|path| {
1330                        self.paths_cache_remove_path(path);
1331                        self.files_mut().remove(path);
1332                    });
1333
1334                    // Fix for when we try to delete empty folders.
1335                    if paths_to_remove.is_empty() {
1336                        vec![ContainerPath::Folder(path); 1]
1337                    } else {
1338                        paths_to_remove.par_iter().map(|path| ContainerPath::File(path.to_string())).collect()
1339                    }
1340                }
1341            }
1342        }
1343    }
1344
1345    /// Returns the full path on disk where this container is stored.
1346    ///
1347    /// # Returns
1348    ///
1349    /// The absolute file path, or an empty string if the container is not backed by a disk file.
1350    fn disk_file_path(&self) -> &str;
1351
1352    /// Returns the filename of the container on disk.
1353    ///
1354    /// Extracts just the filename portion from [`disk_file_path()`](Self::disk_file_path).
1355    ///
1356    /// # Returns
1357    ///
1358    /// The filename as a string, or an empty string if no disk path is set.
1359    fn disk_file_name(&self) -> String {
1360       PathBuf::from(self.disk_file_path()).file_name().unwrap_or_default().to_string_lossy().to_string()
1361    }
1362
1363    /// Returns the byte offset of this container's data within its disk file.
1364    ///
1365    /// This is used for nested containers (e.g., a container embedded within another file).
1366    ///
1367    /// # Returns
1368    ///
1369    /// The offset in bytes, or `0` if the container starts at the beginning of the file.
1370    fn disk_file_offset(&self) -> u64;
1371
1372    /// Checks if a file with the specified path exists in the container.
1373    ///
1374    /// # Parameters
1375    ///
1376    /// - `path`: The file path to check (case-sensitive)
1377    ///
1378    /// # Returns
1379    ///
1380    /// `true` if the file exists, `false` otherwise.
1381    fn has_file(&self, path: &str) -> bool {
1382        self.files().get(path).is_some()
1383    }
1384
1385    /// Checks if a non-empty folder exists at the specified path.
1386    ///
1387    /// A folder is considered to exist if there is at least one file whose path starts
1388    /// with the provided folder path.
1389    ///
1390    /// # Parameters
1391    ///
1392    /// - `path`: The folder path to check
1393    ///
1394    /// # Returns
1395    ///
1396    /// `true` if the folder exists and contains files, `false` otherwise.
1397    ///
1398    /// # Note
1399    ///
1400    /// Empty string always returns `false`. Paths are normalized to end with `/` for matching.
1401    fn has_folder(&self, path: &str) -> bool {
1402        if path.is_empty() {
1403           false
1404        } else {
1405
1406            // Make sure we don't trigger false positives due to similarly started files/folders.
1407            let path = if path.ends_with('/') {
1408                path.to_string()
1409            } else {
1410                let mut path = path.to_string();
1411                path.push('/');
1412                path
1413            };
1414
1415            self.files().keys().any(|x| x.starts_with(&path) && x.len() > path.len())
1416        }
1417    }
1418
1419    /// Returns a reference to an [`RFile`] in the container by path.
1420    ///
1421    /// # Parameters
1422    ///
1423    /// - `path`: The file path to look up
1424    /// - `case_insensitive`: If `true`, performs case-insensitive matching
1425    ///
1426    /// # Returns
1427    ///
1428    /// `Some(&RFile)` if the file exists, `None` otherwise.
1429    fn file(&self, path: &str, case_insensitive: bool) -> Option<&RFile> {
1430        if case_insensitive {
1431            let lower = path.to_lowercase();
1432            self.paths_cache().get(&lower).and_then(|paths| self.files().get(&paths[0]))
1433        } else {
1434            self.files().get(path)
1435        }
1436    }
1437
1438    /// Returns a mutable reference to an [`RFile`] in the container by path.
1439    ///
1440    /// # Parameters
1441    ///
1442    /// - `path`: The file path to look up
1443    /// - `case_insensitive`: If `true`, performs case-insensitive matching
1444    ///
1445    /// # Returns
1446    ///
1447    /// `Some(&mut RFile)` if the file exists, `None` otherwise.
1448    fn file_mut(&mut self, path: &str, case_insensitive: bool) -> Option<&mut RFile> {
1449        if case_insensitive {
1450            let lower = path.to_lowercase();
1451            self.paths_cache().get(&lower).cloned().and_then(|paths| self.files_mut().get_mut(&paths[0]))
1452        } else {
1453            self.files_mut().get_mut(path)
1454        }
1455    }
1456
1457    /// Returns a reference to the internal file map.
1458    ///
1459    /// The map uses file paths as keys and [`RFile`]s as values.
1460    fn files(&self) -> &HashMap<String, RFile>;
1461
1462    /// Returns a mutable reference to the internal file map.
1463    ///
1464    /// The map uses file paths as keys and [`RFile`]s as values.
1465    fn files_mut(&mut self) -> &mut HashMap<String, RFile>;
1466
1467    /// Returns references to all files of the specified types.
1468    ///
1469    /// # Parameters
1470    ///
1471    /// - `file_types`: Slice of [`FileType`]s to filter by
1472    ///
1473    /// # Returns
1474    ///
1475    /// A vector of references to matching files.
1476    fn files_by_type(&self, file_types: &[FileType]) -> Vec<&RFile> {
1477        self.files()
1478            .iter()
1479            .filter(|(_, file)| file_types.contains(&file.file_type))
1480            .map(|(_, file)| file)
1481            .collect()
1482    }
1483
1484    /// Returns mutable references to all files of the specified types.
1485    ///
1486    /// # Parameters
1487    ///
1488    /// - `file_types`: Slice of [`FileType`]s to filter by
1489    ///
1490    /// # Returns
1491    ///
1492    /// A vector of mutable references to matching files.
1493    fn files_by_type_mut(&mut self, file_types: &[FileType]) -> Vec<&mut RFile> {
1494        self.files_mut().par_iter_mut().filter(|(_, file)| file_types.contains(&file.file_type)).map(|(_, file)| file).collect()
1495    }
1496
1497    /// Returns references to files matching the provided [`ContainerPath`].
1498    ///
1499    /// # Parameters
1500    ///
1501    /// - `path`: The container path to match (file or folder)
1502    /// - `case_insensitive`: Enable case-insensitive matching
1503    ///
1504    /// # Returns
1505    ///
1506    /// A vector of references to matching files.
1507    ///
1508    /// # Special Cases
1509    ///
1510    /// `ContainerPath::Folder("")` represents the container root and returns **all** files.
1511    fn files_by_path(&self, path: &ContainerPath, case_insensitive: bool) -> Vec<&RFile> {
1512        match path {
1513            ContainerPath::File(path) => self.file(path, case_insensitive).map(|file| vec![file]).unwrap_or(vec![]),
1514            ContainerPath::Folder(path) => {
1515
1516                // If the path is empty, get everything.
1517                if path.is_empty() {
1518                    self.files().values().collect()
1519                }
1520
1521                else {
1522                    let mut path = if case_insensitive { path.to_lowercase() } else { path.to_owned() };
1523                    if !path.ends_with('/') {
1524                        path.push('/');
1525                    }
1526
1527                    // Otherwise, only get the files under our folder.
1528                    let real_paths = self.paths_cache()
1529                        .par_iter()
1530                        .filter_map(|(lower_path, real_paths)| if lower_path.starts_with(&path) { Some(real_paths) } else { None })
1531                        .map(|paths| paths.iter().map(|path| ContainerPath::File(path.to_owned())).collect::<Vec<_>>())
1532                        .flatten()
1533                        .collect::<Vec<_>>();
1534
1535                    self.files_by_paths(&real_paths, false)
1536                }
1537            },
1538        }
1539    }
1540
1541    /// Returns mutable references to files matching the provided [`ContainerPath`].
1542    ///
1543    /// # Parameters
1544    ///
1545    /// - `path`: The container path to match (file or folder)
1546    /// - `case_insensitive`: Enable case-insensitive matching
1547    ///
1548    /// # Returns
1549    ///
1550    /// A vector of mutable references to matching files.
1551    ///
1552    /// # Special Cases
1553    ///
1554    /// `ContainerPath::Folder("")` represents the container root and returns **all** files.
1555    fn files_by_path_mut(&mut self, path: &ContainerPath, case_insensitive: bool) -> Vec<&mut RFile> {
1556        match path {
1557            ContainerPath::File(path) => self.file_mut(path, case_insensitive).map(|file| vec![file]).unwrap_or(vec![]),
1558            ContainerPath::Folder(path) => {
1559
1560                // If the path is empty, get everything.
1561                if path.is_empty() {
1562                    self.files_mut().values_mut().collect()
1563                }
1564
1565                // Otherwise, only get the files under our folder.
1566                else {
1567                    self.files_mut().par_iter_mut()
1568                        .filter_map(|(key, file)|
1569                            if case_insensitive {
1570                                if starts_with_case_insensitive(key, path) { Some(file) } else { None }
1571                            } else if key.starts_with(path) {
1572                                Some(file)
1573                            } else {
1574                                None
1575                            }
1576                        ).collect::<Vec<&mut RFile>>()
1577                }
1578            },
1579        }
1580    }
1581
1582    /// Returns references to files matching any of the provided [`ContainerPath`]s.
1583    ///
1584    /// # Parameters
1585    ///
1586    /// - `paths`: Slice of container paths to match
1587    /// - `case_insensitive`: Enable case-insensitive matching
1588    ///
1589    /// # Returns
1590    ///
1591    /// A vector of references to all matching files (may contain duplicates if paths overlap).
1592    fn files_by_paths(&self, paths: &[ContainerPath], case_insensitive: bool) -> Vec<&RFile> {
1593        paths.iter()
1594            .flat_map(|path| self.files_by_path(path, case_insensitive))
1595            .collect()
1596    }
1597
1598    /// Returns mutable references to files matching any of the provided [`ContainerPath`]s.
1599    ///
1600    /// This method should be used instead of [`files_by_path_mut()`](Self::files_by_path_mut)
1601    /// when you need mutable references to files across multiple different paths, as it
1602    /// properly handles the borrowing requirements.
1603    ///
1604    /// # Parameters
1605    ///
1606    /// - `paths`: Slice of container paths to match
1607    /// - `case_insensitive`: Enable case-insensitive matching
1608    ///
1609    /// # Returns
1610    ///
1611    /// A vector of mutable references to all matching files (no duplicates).
1612    fn files_by_paths_mut(&mut self, paths: &[ContainerPath], case_insensitive: bool) -> Vec<&mut RFile> {
1613        self.files_mut()
1614            .iter_mut()
1615            .filter(|(file_path, _)| {
1616                paths.iter().any(|path| {
1617                    match path {
1618                        ContainerPath::File(path) => {
1619                            if case_insensitive {
1620                                caseless::canonical_caseless_match_str(file_path, path)
1621                            } else {
1622                                file_path == &path
1623                            }
1624                        }
1625                        ContainerPath::Folder(path) => {
1626                            if case_insensitive {
1627                                starts_with_case_insensitive(file_path, path)
1628                            } else {
1629                                file_path.starts_with(path)
1630                            }
1631                        }
1632                    }
1633                })
1634            })
1635            .map(|(_, file)| file)
1636            .collect()
1637    }
1638
1639    /// Returns references to files matching both type and path criteria.
1640    ///
1641    /// This is a filtered combination of [`files_by_type()`](Self::files_by_type) and
1642    /// [`files_by_paths()`](Self::files_by_paths).
1643    ///
1644    /// # Parameters
1645    ///
1646    /// - `file_types`: Slice of [`FileType`]s to filter by
1647    /// - `paths`: Slice of [`ContainerPath`]s to match
1648    /// - `case_insensitive`: Enable case-insensitive path matching
1649    ///
1650    /// # Returns
1651    ///
1652    /// A vector of references to files matching both the type and path criteria.
1653    fn files_by_type_and_paths(&self, file_types: &[FileType], paths: &[ContainerPath], case_insensitive: bool) -> Vec<&RFile> {
1654        paths.iter()
1655            .flat_map(|path| self.files_by_path(path, case_insensitive)
1656                .into_iter()
1657                .filter(|file| file_types.contains(&file.file_type()))
1658                .collect::<Vec<_>>()
1659            ).collect()
1660    }
1661
1662    /// Returns mutable references to files matching both type and path criteria.
1663    ///
1664    /// This is a filtered combination of [`files_by_type_mut()`](Self::files_by_type_mut) and
1665    /// [`files_by_paths_mut()`](Self::files_by_paths_mut).
1666    ///
1667    /// # Parameters
1668    ///
1669    /// - `file_types`: Slice of [`FileType`]s to filter by
1670    /// - `paths`: Slice of [`ContainerPath`]s to match
1671    /// - `case_insensitive`: Enable case-insensitive path matching
1672    ///
1673    /// # Returns
1674    ///
1675    /// A vector of mutable references to files matching both the type and path criteria.
1676    fn files_by_type_and_paths_mut(&mut self, file_types: &[FileType], paths: &[ContainerPath], case_insensitive: bool) -> Vec<&mut RFile> {
1677        self.files_by_paths_mut(paths, case_insensitive).into_iter().filter(|file| file_types.contains(&file.file_type())).collect()
1678    }
1679
1680    /// Regenerates the internal paths cache from the current file list.
1681    ///
1682    /// The paths cache maps lowercase paths to their actual casing variants, enabling
1683    /// efficient case-insensitive lookups. This method should be called after bulk
1684    /// modifications to the file list.
1685    fn paths_cache_generate(&mut self) {
1686        self.paths_cache_mut().clear();
1687
1688        let mut cache: HashMap<String, Vec<String>> = HashMap::new();
1689        self.files().keys().for_each(|path| {
1690            let lower = path.to_lowercase();
1691            match cache.get_mut(&lower) {
1692                Some(paths) => paths.push(path.to_owned()),
1693                None => { cache.insert(lower, vec![path.to_owned()]); },
1694            }
1695        });
1696
1697        *self.paths_cache_mut() = cache;
1698    }
1699
1700    /// Adds a single path to the paths cache.
1701    ///
1702    /// This is more efficient than regenerating the entire cache when adding individual files.
1703    ///
1704    /// # Parameters
1705    ///
1706    /// - `path`: The file path to add (with original casing)
1707    fn paths_cache_insert_path(&mut self, path: &str) {
1708        let path_lower = path.to_lowercase();
1709        match self.paths_cache_mut().get_mut(&path_lower) {
1710            Some(paths) => if paths.iter().all(|x| x != path) {
1711                paths.push(path.to_owned());
1712            }
1713            None => { self.paths_cache_mut().insert(path_lower, vec![path.to_owned()]); }
1714        }
1715    }
1716
1717    /// Removes a single path from the paths cache.
1718    ///
1719    /// This is more efficient than regenerating the entire cache when removing individual files.
1720    ///
1721    /// # Parameters
1722    ///
1723    /// - `path`: The file path to remove (with original casing)
1724    ///
1725    /// # Note
1726    ///
1727    /// Reserved paths (notes, settings) are automatically skipped.
1728    fn paths_cache_remove_path(&mut self, path: &str) {
1729        let path_lower = path.to_lowercase();
1730
1731        // Skip reserved paths when using this.
1732        if path_lower == RESERVED_NAME_NOTES || path_lower == RESERVED_NAME_SETTINGS {
1733            return;
1734        }
1735
1736        match self.paths_cache_mut().get_mut(&path_lower) {
1737            Some(paths) => {
1738                match paths.iter().position(|x| x == path) {
1739                    Some(pos) => {
1740                        paths.remove(pos);
1741                        if paths.is_empty() {
1742                            self.paths_cache_mut().remove(&path_lower);
1743                        }
1744                    },
1745                    None => { warn!("remove_path received a valid path, but we don't have casing equivalence for it. This is a bug. {path_lower}, {path}"); },
1746                }
1747            }
1748            None => { warn!("remove_path received an invalid path. This is a bug. {path_lower}, {path}"); },
1749        }
1750    }
1751
1752    /// Returns the paths cache mapping lowercase paths to their original-cased variants.
1753    ///
1754    /// This cache enables efficient case-insensitive file lookups. The map structure is:
1755    /// `lowercase_path -> vec![OriginalCased1, OriginalCased2, ...]`
1756    ///
1757    /// # Important
1758    ///
1759    /// If you manipulate the file list directly (via [`files_mut()`](Self::files_mut)),
1760    /// you **must** update this cache using [`paths_cache_insert_path()`](Self::paths_cache_insert_path),
1761    /// [`paths_cache_remove_path()`](Self::paths_cache_remove_path), or
1762    /// [`paths_cache_generate()`](Self::paths_cache_generate).
1763    fn paths_cache(&self) -> &HashMap<String, Vec<String>>;
1764
1765    /// Returns a mutable reference to the paths cache.
1766    ///
1767    /// See [`paths_cache()`](Self::paths_cache) for details on cache structure and maintenance.
1768    fn paths_cache_mut(&mut self) -> &mut HashMap<String, Vec<String>>;
1769
1770    /// Returns a set of all folder paths contained within the container.
1771    ///
1772    /// This method analyzes file paths to extract unique folder hierarchies.
1773    ///
1774    /// # Returns
1775    ///
1776    /// A set of folder paths (without trailing slashes). Root-level files contribute no entries.
1777    fn paths_folders_raw(&self) -> HashSet<String> {
1778        self.files()
1779            .par_iter()
1780            .filter_map(|(path, _)| {
1781                let file_path_split = path.split('/').collect::<Vec<&str>>();
1782                let folder_path_len = file_path_split.len() - 1;
1783                if folder_path_len == 0 {
1784                    None
1785                } else {
1786
1787                    let mut paths = Vec::with_capacity(folder_path_len);
1788
1789                    for (index, folder) in file_path_split.iter().enumerate() {
1790                        if index < path.len() - 1 && !folder.is_empty() {
1791                            paths.push(file_path_split[0..=index].join("/"))
1792                        }
1793                    }
1794
1795                    Some(paths)
1796                }
1797            })
1798            .flatten()
1799            .collect::<HashSet<String>>()
1800    }
1801
1802    /// This method returns the list of [ContainerPath] corresponding to RFiles within the provided Container.
1803    fn paths(&self) -> Vec<ContainerPath> {
1804        self.files()
1805            .par_iter()
1806            .map(|(path, _)| ContainerPath::File(path.to_owned()))
1807            .collect()
1808    }
1809
1810    /// This method returns the list of paths (as [&str]) corresponding to RFiles within the provided Container.
1811    fn paths_raw(&self) -> Vec<&str> {
1812        self.files()
1813            .par_iter()
1814            .map(|(path, _)| &**path)
1815            .collect()
1816    }
1817
1818    /// This function returns the list of paths (as [String]) corresponding to RFiles that match the provided [ContainerPath].
1819    fn paths_raw_from_container_path(&self, path: &ContainerPath) -> Vec<String> {
1820        match path {
1821            ContainerPath::File(path) => vec![path.to_owned(); 1],
1822            ContainerPath::Folder(path) => {
1823
1824                // If the path is empty, get everything.
1825                if path.is_empty() {
1826                    self.paths_raw().iter().map(|x| x.to_string()).collect()
1827                }
1828
1829                // Otherwise, only get the paths under our folder.
1830                else {
1831                    self.files().par_iter()
1832                        .filter_map(|(key, file)|
1833                            if key.starts_with(path) {
1834                                Some(file.path_in_container_raw().to_owned())
1835                            } else {
1836                                None
1837                            }
1838                        ).collect::<Vec<String>>()
1839                }
1840            },
1841        }
1842    }
1843
1844    /// This method returns the `Last modified date` stored on the provided Container, in seconds.
1845    ///
1846    /// A default implementation that returns `0` is provided for Container types that don't support internal timestamps.
1847    ///
1848    /// Implementors should return `0` if the Container doesn't have a file on disk yet.
1849    fn internal_timestamp(&self) -> u64 {
1850       0
1851    }
1852
1853    /// This method returns the `Last modified date` the filesystem reports for the container file, in seconds.
1854    ///
1855    /// Implementors should return `0` if the Container doesn't have a file on disk yet.
1856    fn local_timestamp(&self) -> u64;
1857
1858    /// This function preloads to memory any lazy-loaded RFile within this container.
1859    fn preload(&mut self) -> Result<()> {
1860        self.files_mut()
1861            .into_par_iter()
1862            .try_for_each(|(_, rfile)| rfile.encode(&None, false, true, false).map(|_| ()))
1863    }
1864
1865    /// This function allows you to *move* multiple RFiles or folders of RFiles from one folder to another.
1866    ///
1867    /// It returns a list with all the new [ContainerPath].
1868    fn move_paths(&mut self, in_out_paths: &[(ContainerPath, ContainerPath)]) -> Result<Vec<(ContainerPath, ContainerPath)>> {
1869        let mut successes = vec![];
1870        for (source_path, destination_path) in in_out_paths {
1871            successes.append(&mut self.move_path(source_path, destination_path)?);
1872        }
1873
1874        Ok(successes)
1875    }
1876
1877    /// This function allows you to *move* any RFile or folder of RFiles from one folder to another.
1878    ///
1879    /// It returns a list with all the new [ContainerPath].
1880    fn move_path(&mut self, source_path: &ContainerPath, destination_path: &ContainerPath) -> Result<Vec<(ContainerPath, ContainerPath)>> {
1881        match source_path {
1882            ContainerPath::File(source_path) => match destination_path {
1883                ContainerPath::File(destination_path) => {
1884                    if destination_path.is_empty() {
1885                        return Err(RLibError::EmptyDestiny);
1886                    }
1887
1888                    self.paths_cache_remove_path(source_path);
1889                    let mut moved = self
1890                        .files_mut()
1891                        .remove(source_path)
1892                        .ok_or_else(|| RLibError::FileNotFound(source_path.to_string()))?;
1893
1894                    moved.set_path_in_container_raw(destination_path);
1895
1896                    self.insert(moved).map(|x| match x {
1897                        Some(x) => vec![(ContainerPath::File(source_path.to_string()), x); 1],
1898                        None => Vec::with_capacity(0)
1899                    })
1900                },
1901                ContainerPath::Folder(_) => unreachable!("move_path_1"),
1902            },
1903            ContainerPath::Folder(source_path) => match destination_path {
1904                ContainerPath::File(_) => unreachable!("move_path_2"),
1905                ContainerPath::Folder(destination_path) => {
1906                    if destination_path.is_empty() {
1907                        return Err(RLibError::EmptyDestiny);
1908                    }
1909
1910                    // Fix to avoid false positives.
1911                    let mut source_path_end = source_path.to_owned();
1912                    if !source_path_end.ends_with('/') {
1913                        source_path_end.push('/');
1914                    }
1915
1916                    let moved_paths = self.files()
1917                        .par_iter()
1918                        .filter_map(|(path, _)| if path.starts_with(&source_path_end) { Some(path.to_owned()) } else { None })
1919                        .collect::<Vec<_>>();
1920
1921                    let moved = moved_paths.iter()
1922                        .filter_map(|x| {
1923                            self.paths_cache_remove_path(x);
1924                            self.files_mut().remove(x)
1925                        })
1926                        .collect::<Vec<_>>();
1927
1928                    let mut new_paths = Vec::with_capacity(moved.len());
1929                    for mut moved in moved {
1930                        let old_path = moved.path_in_container();
1931                        let new_path = moved.path_in_container_raw().replacen(source_path, destination_path, 1);
1932                        moved.set_path_in_container_raw(&new_path);
1933
1934                        if let Some(new_path) = self.insert(moved)? {
1935                            new_paths.push((old_path, new_path));
1936                        }
1937                    }
1938
1939                    Ok(new_paths)
1940                },
1941            },
1942        }
1943    }
1944
1945    /// This function removes all not-in-memory-already Files from the Container.
1946    ///
1947    /// Used for removing possibly corrupted RFiles from the Container in order to sanitize it.
1948    ///
1949    /// BE CAREFUL WITH USING THIS. IT MAY (PROBABLY WILL) CAUSE DATA LOSSES.
1950    fn clean_undecoded(&mut self) {
1951        self.files_mut().retain(|_, file| file.decoded().is_ok() || file.cached().is_ok());
1952    }
1953}
1954
1955//----------------------------------------------------------------//
1956//                        Implementations
1957//----------------------------------------------------------------//
1958
1959impl RFile {
1960
1961    /// This function creates a RFile from a lazy-loaded file inside a Container.
1962    ///
1963    /// About the parameters:
1964    /// - `container`: The container this RFile is on.
1965    /// - `size`: Size in bytes of the RFile.
1966    /// - `is_compressed`: If the RFile is compressed.
1967    /// - `is_encrypted`: If the RFile is encrypted.
1968    /// - `data_pos`: Byte offset of the data from the beginning of the Container.
1969    /// - `file_timestamp`: Timestamp of this specific file (not of the container, but the file). If it doesn't have one, pass 0.
1970    /// - `path_in_container`: Path of the RFile in the container.
1971    ///
1972    /// NOTE: Remember to call `guess_file_type` after this to properly set the FileType.
1973    pub fn new_from_container<C: Container>(
1974        container: &C,
1975        size: u64,
1976        is_compressed: bool,
1977        is_encrypted: Option<PFHVersion>,
1978        data_pos: u64,
1979        file_timestamp: u64,
1980        path_in_container: &str,
1981    ) -> Result<Self> {
1982        let on_disk = OnDisk {
1983            path: container.disk_file_path().to_owned(),
1984            timestamp: container.local_timestamp(),
1985            start: container.disk_file_offset() + data_pos,
1986            size,
1987            is_compressed,
1988            is_encrypted,
1989        };
1990
1991        let rfile = Self {
1992            path: path_in_container.to_owned(),
1993            timestamp: if file_timestamp == 0 { None } else { Some(file_timestamp) },
1994            file_type: FileType::Unknown,
1995            container_name: Some(container.disk_file_name()),
1996            data: RFileInnerData::OnDisk(on_disk)
1997        };
1998
1999        Ok(rfile)
2000    }
2001
2002    /// This function creates a RFile from a path on disk.
2003    ///
2004    /// This may fail if the file doesn't exist or errors out when trying to be read for metadata.
2005    ///
2006    /// NOTE: Remember to call `guess_file_type` after this to properly set the FileType.
2007    pub fn new_from_file(path: &str) -> Result<Self> {
2008        let path_checked = PathBuf::from(path);
2009        if !path_checked.is_file() {
2010            return Err(RLibError::FileNotFound(path.to_owned()));
2011        }
2012
2013        let mut file = File::open(path)?;
2014        let on_disk = OnDisk {
2015            path: path.to_owned(),
2016            timestamp: last_modified_time_from_file(&file)?,
2017            start: 0,
2018            size: file.len()?,
2019            is_compressed: false,
2020            is_encrypted: None,
2021        };
2022
2023
2024        let rfile = Self {
2025            path: path.to_owned(),
2026            timestamp: Some(on_disk.timestamp),
2027            file_type: FileType::Unknown,
2028            container_name: None,
2029            data: RFileInnerData::OnDisk(on_disk)
2030        };
2031
2032        Ok(rfile)
2033    }
2034
2035    /// This function creates a RFile from a path on disk.
2036    ///
2037    /// This may fail if the file doesn't exist or errors out when trying to be read for metadata.
2038    ///
2039    /// NOTE: Remember to call `guess_file_type` after this to properly set the FileType.
2040    pub fn new_from_file_path(path: &Path) -> Result<Self> {
2041        let path = path.to_string_lossy().to_string();
2042        Self::new_from_file(&path)
2043    }
2044
2045    /// This function creates a RFile from raw data on memory.
2046    ///
2047    /// NOTE: Remember to call `guess_file_type` after this to properly set the FileType.
2048    pub fn new_from_vec(data: &[u8], file_type: FileType, timestamp: u64, path: &str) -> Self {
2049        Self {
2050            path: path.to_owned(),
2051            timestamp: if timestamp == 0 { None } else { Some(timestamp) },
2052            file_type,
2053            container_name: None,
2054            data: RFileInnerData::Cached(data.to_vec())
2055        }
2056    }
2057
2058    /// This function creates a RFile from an RFileDecoded on memory.
2059    ///
2060    /// NOTE: Remember to call `guess_file_type` after this to properly set the FileType.
2061    pub fn new_from_decoded(data: &RFileDecoded, timestamp: u64, path: &str) -> Self {
2062        Self {
2063            path: path.to_owned(),
2064            timestamp: if timestamp == 0 { None } else { Some(timestamp) },
2065            file_type: FileType::from(data),
2066            container_name: None,
2067            data: RFileInnerData::Decoded(Box::new(data.clone()))
2068        }
2069    }
2070
2071    /// This function returns a reference to the cached data of an RFile, if said RFile has been cached. If not, it returns an error.
2072    ///
2073    /// Useful for accessing preloaded data.
2074    pub fn cached(&self) -> Result<&[u8]> {
2075        match self.data {
2076            RFileInnerData::Cached(ref data) => Ok(data),
2077            _ => Err(RLibError::FileNotCached(self.path_in_container_raw().to_string()))
2078        }
2079    }
2080
2081    /// This function returns a mutable reference to the cached data of an RFile, if said RFile has been cached. If not, it returns an error.
2082    ///
2083    /// Useful for accessing preloaded data.
2084    pub fn cached_mut(&mut self) -> Result<&mut Vec<u8>> {
2085        match self.data {
2086            RFileInnerData::Cached(ref mut data) => Ok(data),
2087            _ => Err(RLibError::FileNotCached(self.path_in_container_raw().to_string()))
2088        }
2089    }
2090
2091    /// This function returns a reference to the decoded data of an RFile, if said RFile has been decoded. If not, it returns an error.
2092    ///
2093    /// Useful for accessing preloaded data.
2094    pub fn decoded(&self) -> Result<&RFileDecoded> {
2095        match self.data {
2096            RFileInnerData::Decoded(ref data) => Ok(data),
2097            _ => Err(RLibError::FileNotDecoded(self.path_in_container_raw().to_string()))
2098        }
2099    }
2100
2101    /// This function returns a mutable reference to the decoded data of an RFile, if said RFile has been decoded. If not, it returns an error.
2102    ///
2103    /// Useful for accessing preloaded data.
2104    pub fn decoded_mut(&mut self) -> Result<&mut RFileDecoded> {
2105        match self.data {
2106            RFileInnerData::Decoded(ref mut data) => Ok(data),
2107            _ => Err(RLibError::FileNotDecoded(self.path_in_container_raw().to_string()))
2108        }
2109    }
2110
2111    /// This function replace any data a RFile has with the provided raw data.
2112    pub fn set_cached(&mut self, data: &[u8]) {
2113        self.data = RFileInnerData::Cached(data.to_vec());
2114    }
2115
2116    /// This function allows to replace the inner decoded data of a RFile with another. It'll fail if the decoded data is not valid for the file's type.
2117    pub fn set_decoded(&mut self, decoded: RFileDecoded) -> Result<()> {
2118        match (self.file_type(), &decoded) {
2119            (FileType::Anim, &RFileDecoded::Anim(_)) |
2120            (FileType::AnimFragmentBattle, &RFileDecoded::AnimFragmentBattle(_)) |
2121            (FileType::AnimPack, &RFileDecoded::AnimPack(_)) |
2122            (FileType::AnimsTable, &RFileDecoded::AnimsTable(_)) |
2123            (FileType::Atlas, &RFileDecoded::Atlas(_)) |
2124            (FileType::Audio, &RFileDecoded::Audio(_)) |
2125            (FileType::BMD, &RFileDecoded::BMD(_)) |
2126            (FileType::BMDVegetation, &RFileDecoded::BMDVegetation(_)) |
2127            (FileType::Dat, &RFileDecoded::Dat(_)) |
2128            (FileType::DB, &RFileDecoded::DB(_)) |
2129            (FileType::ESF, &RFileDecoded::ESF(_)) |
2130            (FileType::Font, &RFileDecoded::Font(_)) |
2131            (FileType::GroupFormations, &RFileDecoded::GroupFormations(_)) |
2132            (FileType::HlslCompiled, &RFileDecoded::HlslCompiled(_)) |
2133            (FileType::Image, &RFileDecoded::Image(_)) |
2134            (FileType::Loc, &RFileDecoded::Loc(_)) |
2135            (FileType::MatchedCombat, &RFileDecoded::MatchedCombat(_)) |
2136            (FileType::Pack, &RFileDecoded::Pack(_)) |
2137            (FileType::PortraitSettings, &RFileDecoded::PortraitSettings(_)) |
2138            (FileType::RigidModel, &RFileDecoded::RigidModel(_)) |
2139            (FileType::SoundBank, &RFileDecoded::SoundBank(_)) |
2140            (FileType::Text, &RFileDecoded::Text(_)) |
2141            (FileType::UIC, &RFileDecoded::UIC(_)) |
2142            (FileType::UnitVariant, &RFileDecoded::UnitVariant(_)) |
2143            (FileType::Video, &RFileDecoded::Video(_)) |
2144            (FileType::VMD, &RFileDecoded::VMD(_)) |
2145            (FileType::WSModel, &RFileDecoded::WSModel(_)) |
2146            (FileType::Unknown, &RFileDecoded::Unknown(_)) => self.data = RFileInnerData::Decoded(Box::new(decoded)),
2147            _ => return Err(RLibError::DecodedDataDoesNotMatchFileType(self.file_type(), From::from(&decoded)))
2148        }
2149
2150        Ok(())
2151    }
2152
2153    /// This function decodes an RFile from binary data, optionally caching and returning the decoded RFile.
2154    ///
2155    /// About the arguments:
2156    ///
2157    /// - `extra_data`: any data needed to decode specific file types. Check each file type for info about what do each file type need.
2158    /// - `keep_in_cache`: if true, the data will be cached on memory.
2159    /// - `return_data`: if true, the decoded data will be returned.
2160    ///
2161    /// NOTE: Passing `keep_in_cache` and `return_data` at false causes this function to decode the RFile and
2162    /// immediately drop the resulting data.
2163    pub fn decode(&mut self, extra_data: &Option<DecodeableExtraData>, keep_in_cache: bool, return_data: bool) -> Result<Option<RFileDecoded>> {
2164        let mut already_decoded = false;
2165        let decoded = match &self.data {
2166
2167            // If the data is already decoded, just return a copy of it.
2168            RFileInnerData::Decoded(data) => {
2169                already_decoded = true;
2170
2171                // Microoptimization: don't clone data if we're not going to use it.
2172                if !return_data {
2173                    return Ok(None);
2174                }
2175
2176                *data.clone()
2177            },
2178
2179            // If the data is on memory but not yet decoded, decode it.
2180            RFileInnerData::Cached(data) => {
2181
2182                // Copy the provided extra data (if any), then replace the file-specific stuff.
2183                let mut extra_data = match extra_data {
2184                    Some(extra_data) => extra_data.clone(),
2185                    None => DecodeableExtraData::default(),
2186                };
2187                extra_data.file_name = self.file_name();
2188                extra_data.data_size = data.len() as u64;
2189
2190                // Some types require extra data specific for them to be added to the extra data before decoding.
2191                let mut data = Cursor::new(data);
2192                match self.file_type {
2193                    FileType::Anim => RFileDecoded::Anim(Anim::decode(&mut data, &Some(extra_data))?),
2194                    FileType::AnimFragmentBattle => RFileDecoded::AnimFragmentBattle(AnimFragmentBattle::decode(&mut data, &Some(extra_data))?),
2195                    FileType::AnimPack => RFileDecoded::AnimPack(AnimPack::decode(&mut data, &Some(extra_data))?),
2196                    FileType::AnimsTable => RFileDecoded::AnimsTable(AnimsTable::decode(&mut data, &Some(extra_data))?),
2197                    FileType::Atlas => RFileDecoded::Atlas(Atlas::decode(&mut data, &Some(extra_data))?),
2198                    FileType::Audio => RFileDecoded::Audio(Audio::decode(&mut data, &Some(extra_data))?),
2199                    FileType::BMD => RFileDecoded::BMD(Box::new(Bmd::decode(&mut data, &Some(extra_data))?)),
2200                    FileType::BMDVegetation => RFileDecoded::BMDVegetation(BmdVegetation::decode(&mut data, &Some(extra_data))?),
2201                    FileType::Dat => RFileDecoded::Dat(Dat::decode(&mut data, &Some(extra_data))?),
2202                    FileType::DB => {
2203
2204                        if extra_data.table_name.is_none() {
2205                            extra_data.table_name = self.db_table_name_from_path();
2206                        }
2207                        RFileDecoded::DB(DB::decode(&mut data, &Some(extra_data))?)
2208                    },
2209                    FileType::ESF => RFileDecoded::ESF(ESF::decode(&mut data, &Some(extra_data))?),
2210                    FileType::Font => RFileDecoded::Font(Font::decode(&mut data, &Some(extra_data))?),
2211                    FileType::GroupFormations => RFileDecoded::GroupFormations(GroupFormations::decode(&mut data, &Some(extra_data))?),
2212                    FileType::HlslCompiled => RFileDecoded::HlslCompiled(HlslCompiled::decode(&mut data, &Some(extra_data))?),
2213                    FileType::Image => {
2214
2215                        if self.path.ends_with(".dds") {
2216                            extra_data.is_dds = true;
2217                        }
2218
2219                        RFileDecoded::Image(Image::decode(&mut data, &Some(extra_data))?)
2220                    },
2221                    FileType::Loc => RFileDecoded::Loc(Loc::decode(&mut data, &Some(extra_data))?),
2222                    FileType::MatchedCombat => RFileDecoded::MatchedCombat(MatchedCombat::decode(&mut data, &Some(extra_data))?),
2223                    FileType::Pack => RFileDecoded::Pack(Pack::decode(&mut data, &Some(extra_data))?),
2224                    FileType::PortraitSettings => RFileDecoded::PortraitSettings(PortraitSettings::decode(&mut data, &Some(extra_data))?),
2225                    FileType::RigidModel => RFileDecoded::RigidModel(RigidModel::decode(&mut data, &Some(extra_data))?),
2226                    FileType::SoundBank => RFileDecoded::SoundBank(SoundBank::decode(&mut data, &Some(extra_data))?),
2227                    FileType::Text => RFileDecoded::Text(Text::decode(&mut data, &Some(extra_data))?),
2228                    FileType::UIC => RFileDecoded::UIC(UIC::decode(&mut data, &Some(extra_data))?),
2229                    FileType::UnitVariant => RFileDecoded::UnitVariant(UnitVariant::decode(&mut data, &Some(extra_data))?),
2230                    FileType::Unknown => RFileDecoded::Unknown(Unknown::decode(&mut data, &Some(extra_data))?),
2231                    FileType::Video => RFileDecoded::Video(Video::decode(&mut data, &Some(extra_data))?),
2232                    FileType::VMD => RFileDecoded::VMD(Text::decode(&mut data, &Some(extra_data))?),
2233                    FileType::WSModel => RFileDecoded::WSModel(Text::decode(&mut data, &Some(extra_data))?),
2234                }
2235            },
2236
2237            // If the data is not yet in memory, it depends:
2238            // - If it's something we can lazy-load and we want to, decode it directly from disk.
2239            // - If it's not, load it to memory and decode it from there.
2240            RFileInnerData::OnDisk(data) => {
2241
2242                // Copy the provided extra data (if any), then replace the file-specific stuff.
2243                let raw_data = data.read()?;
2244                let mut extra_data = match extra_data {
2245                    Some(extra_data) => extra_data.clone(),
2246                    None => DecodeableExtraData::default(),
2247                };
2248
2249                extra_data.file_name = self.file_name();
2250                extra_data.data_size = raw_data.len() as u64;
2251
2252                // These are the easy types: just load the data to memory, and decode.
2253                let mut data = Cursor::new(raw_data);
2254                match self.file_type {
2255                    FileType::Anim => RFileDecoded::Anim(Anim::decode(&mut data, &Some(extra_data))?),
2256                    FileType::AnimFragmentBattle => RFileDecoded::AnimFragmentBattle(AnimFragmentBattle::decode(&mut data, &Some(extra_data))?),
2257                    FileType::AnimsTable => RFileDecoded::AnimsTable(AnimsTable::decode(&mut data, &Some(extra_data))?),
2258                    FileType::AnimPack => RFileDecoded::AnimPack(AnimPack::decode(&mut data, &Some(extra_data))?),
2259                    FileType::Atlas => RFileDecoded::Atlas(Atlas::decode(&mut data, &Some(extra_data))?),
2260                    FileType::Audio => RFileDecoded::Audio(Audio::decode(&mut data, &Some(extra_data))?),
2261                    FileType::BMD => RFileDecoded::BMD(Box::new(Bmd::decode(&mut data, &Some(extra_data))?)),
2262                    FileType::BMDVegetation => RFileDecoded::BMDVegetation(BmdVegetation::decode(&mut data, &Some(extra_data))?),
2263                    FileType::Dat => RFileDecoded::Dat(Dat::decode(&mut data, &Some(extra_data))?),
2264                    FileType::DB => {
2265
2266                        if extra_data.table_name.is_none() {
2267                            extra_data.table_name = self.db_table_name_from_path();
2268                        }
2269                        RFileDecoded::DB(DB::decode(&mut data, &Some(extra_data))?)
2270                    },
2271                    FileType::ESF => RFileDecoded::ESF(ESF::decode(&mut data, &Some(extra_data))?),
2272                    FileType::Font => RFileDecoded::Font(Font::decode(&mut data, &Some(extra_data))?),
2273                    FileType::GroupFormations => RFileDecoded::GroupFormations(GroupFormations::decode(&mut data, &Some(extra_data))?),
2274                    FileType::HlslCompiled => RFileDecoded::HlslCompiled(HlslCompiled::decode(&mut data, &Some(extra_data))?),
2275                    FileType::Image => {
2276
2277                        if self.path.ends_with(".dds") {
2278                            extra_data.is_dds = true;
2279                        }
2280
2281                        RFileDecoded::Image(Image::decode(&mut data, &Some(extra_data))?)
2282                    },
2283                    FileType::Loc => RFileDecoded::Loc(Loc::decode(&mut data, &Some(extra_data))?),
2284                    FileType::MatchedCombat => RFileDecoded::MatchedCombat(MatchedCombat::decode(&mut data, &Some(extra_data))?),
2285                    FileType::Pack => RFileDecoded::Pack(Pack::decode(&mut data, &Some(extra_data))?),
2286                    FileType::PortraitSettings => RFileDecoded::PortraitSettings(PortraitSettings::decode(&mut data, &Some(extra_data))?),
2287                    FileType::RigidModel => RFileDecoded::RigidModel(RigidModel::decode(&mut data, &Some(extra_data))?),
2288                    FileType::SoundBank => RFileDecoded::SoundBank(SoundBank::decode(&mut data, &Some(extra_data))?),
2289                    FileType::Text => RFileDecoded::Text(Text::decode(&mut data, &Some(extra_data))?),
2290                    FileType::UIC => RFileDecoded::UIC(UIC::decode(&mut data, &Some(extra_data))?),
2291                    FileType::UnitVariant => RFileDecoded::UnitVariant(UnitVariant::decode(&mut data, &Some(extra_data))?),
2292                    FileType::Unknown => RFileDecoded::Unknown(Unknown::decode(&mut data, &Some(extra_data))?),
2293                    FileType::Video => RFileDecoded::Video(Video::decode(&mut data, &Some(extra_data))?),
2294                    FileType::VMD => RFileDecoded::VMD(Text::decode(&mut data, &Some(extra_data))?),
2295                    FileType::WSModel => RFileDecoded::WSModel(Text::decode(&mut data, &Some(extra_data))?),
2296                }
2297            },
2298        };
2299
2300        // If we're returning data, clone it. If not, skip the clone.
2301        if !already_decoded && keep_in_cache && return_data {
2302            self.data = RFileInnerData::Decoded(Box::new(decoded.clone()));
2303        } else if !already_decoded && keep_in_cache && !return_data{
2304            self.data = RFileInnerData::Decoded(Box::new(decoded));
2305            return Ok(None)
2306        }
2307
2308        if return_data {
2309            Ok(Some(decoded))
2310        } else {
2311            Ok(None)
2312        }
2313    }
2314
2315    /// This function encodes an RFile to binary, optionally caching and returning the data.
2316    ///
2317    /// About the arguments:
2318    /// - `extra_data`: any data needed to encode specific file types. Check each file type for info about what do each file type need.
2319    /// - `move_decoded_to_cache`: if true, the decoded data will be dropped in favor of undecoded cached data.
2320    /// - `move_undecoded_to_cache`: if true, the data will be cached on memory.
2321    /// - `return_data`: if true, the data will be returned.
2322    pub fn encode(&mut self, extra_data: &Option<EncodeableExtraData>, move_decoded_to_cache: bool, move_undecoded_to_cache: bool, return_data: bool) -> Result<Option<Vec<u8>>> {
2323        let mut previously_decoded = false;
2324        let mut already_encoded = false;
2325        let mut previously_undecoded = false;
2326
2327        let encoded = match &mut self.data {
2328            RFileInnerData::Decoded(data) => {
2329                previously_decoded = true;
2330                let mut buffer = vec![];
2331                match &mut **data {
2332                    RFileDecoded::Anim(data) => data.encode(&mut buffer, extra_data)?,
2333                    RFileDecoded::AnimFragmentBattle(data) => data.encode(&mut buffer, extra_data)?,
2334                    RFileDecoded::AnimPack(data) => data.encode(&mut buffer, extra_data)?,
2335                    RFileDecoded::AnimsTable(data) => data.encode(&mut buffer, extra_data)?,
2336                    RFileDecoded::Atlas(data) => data.encode(&mut buffer, extra_data)?,
2337                    RFileDecoded::Audio(data) => data.encode(&mut buffer, extra_data)?,
2338                    RFileDecoded::BMD(data) => data.encode(&mut buffer, extra_data)?,
2339                    RFileDecoded::BMDVegetation(data) => data.encode(&mut buffer, extra_data)?,
2340                    RFileDecoded::Dat(data) => data.encode(&mut buffer, extra_data)?,
2341                    RFileDecoded::DB(data) => data.encode(&mut buffer, extra_data)?,
2342                    RFileDecoded::ESF(data) => data.encode(&mut buffer, extra_data)?,
2343                    RFileDecoded::Font(data) => data.encode(&mut buffer, extra_data)?,
2344                    RFileDecoded::GroupFormations(data) => data.encode(&mut buffer, extra_data)?,
2345                    RFileDecoded::HlslCompiled(data) => data.encode(&mut buffer, extra_data)?,
2346                    RFileDecoded::Image(data) => data.encode(&mut buffer, extra_data)?,
2347                    RFileDecoded::Loc(data) => data.encode(&mut buffer, extra_data)?,
2348                    RFileDecoded::MatchedCombat(data) => data.encode(&mut buffer, extra_data)?,
2349                    RFileDecoded::Pack(data) => data.encode(&mut buffer, extra_data)?,
2350                    RFileDecoded::PortraitSettings(data) => data.encode(&mut buffer, extra_data)?,
2351                    RFileDecoded::RigidModel(data) => data.encode(&mut buffer, extra_data)?,
2352                    RFileDecoded::SoundBank(data) => data.encode(&mut buffer, extra_data)?,
2353                    RFileDecoded::Text(data) => data.encode(&mut buffer, extra_data)?,
2354                    RFileDecoded::UIC(data) => data.encode(&mut buffer, extra_data)?,
2355                    RFileDecoded::UnitVariant(data) => data.encode(&mut buffer, extra_data)?,
2356                    RFileDecoded::Unknown(data) => data.encode(&mut buffer, extra_data)?,
2357                    RFileDecoded::Video(data) => data.encode(&mut buffer, extra_data)?,
2358                    RFileDecoded::VMD(data) => data.encode(&mut buffer, extra_data)?,
2359                    RFileDecoded::WSModel(data) => data.encode(&mut buffer, extra_data)?,
2360                }
2361
2362                buffer
2363            },
2364            RFileInnerData::Cached(data) => {
2365                already_encoded = true;
2366                data.to_vec()
2367            },
2368            RFileInnerData::OnDisk(data) => {
2369                previously_undecoded = true;
2370                data.read()?
2371            },
2372        };
2373
2374        // If the RFile was already decoded.
2375        if previously_decoded {
2376            if move_decoded_to_cache {
2377                if return_data {
2378                    self.data = RFileInnerData::Cached(encoded.to_vec());
2379                    Ok(Some(encoded))
2380                } else {
2381                    self.data = RFileInnerData::Cached(encoded);
2382                    Ok(None)
2383                }
2384            } else if return_data {
2385                Ok(Some(encoded))
2386            } else {
2387                Ok(None)
2388            }
2389        }
2390
2391        // If the RFile was not even loaded.
2392        else if previously_undecoded {
2393            if move_undecoded_to_cache {
2394                if return_data {
2395                    self.data = RFileInnerData::Cached(encoded.to_vec());
2396                    Ok(Some(encoded))
2397                } else {
2398                    self.data = RFileInnerData::Cached(encoded);
2399                    Ok(None)
2400                }
2401            } else if return_data {
2402                Ok(Some(encoded))
2403            } else {
2404                Ok(None)
2405            }
2406        }
2407
2408        // If the RFile was already encoded and loaded.
2409        else if already_encoded && return_data {
2410            Ok(Some(encoded))
2411        } else {
2412            Ok(None)
2413        }
2414    }
2415
2416    /// This function loads the data of an RFile to memory if it's not yet loaded.
2417    ///
2418    /// If it has already been loaded either to cache, or for decoding, this does nothing.
2419    pub fn load(&mut self) -> Result<()> {
2420       let loaded = match &self.data {
2421            RFileInnerData::Decoded(_) |
2422            RFileInnerData::Cached(_) => return Ok(()),
2423            RFileInnerData::OnDisk(data) => data.read()?,
2424        };
2425
2426        // Piece of code to find text files we do not support yet. Needs enabling the content_inspector crate.
2427        #[cfg(feature = "enable_content_inspector")]
2428        if self.file_type() == FileType::Unknown && !loaded.is_empty() && content_inspector::inspect(&loaded).is_text() {
2429            dbg!(self.path_in_container_raw());
2430        }
2431
2432        self.data = RFileInnerData::Cached(loaded);
2433        Ok(())
2434    }
2435
2436    /// This function returns a copy of the `Last modified date` of this RFile, if any.
2437    pub fn timestamp(&self) -> Option<u64> {
2438        self.timestamp
2439    }
2440
2441    /// This function returns a copy of the FileType of this RFile.
2442    pub fn file_type(&self) -> FileType {
2443        self.file_type
2444    }
2445
2446    /// This function returns the file name if this RFile, if it has one.
2447    pub fn file_name(&self) -> Option<&str> {
2448        self.path_in_container_raw().split('/').next_back()
2449    }
2450
2451    /// This function returns the file name of the container this RFile originates from, if any.
2452    pub fn container_name(&self) -> &Option<String> {
2453        &self.container_name
2454    }
2455
2456    /// This function returns the [ContainerPath] corresponding to this file.
2457    pub fn path_in_container(&self) -> ContainerPath {
2458        ContainerPath::File(self.path.to_owned())
2459    }
2460
2461    /// This function returns the [ContainerPath] corresponding to this file as an [&str].
2462    pub fn path_in_container_raw(&self) -> &str {
2463        &self.path
2464    }
2465
2466    /// This function returns the [ContainerPath] corresponding to this file as a [Vec] of [&str].
2467    pub fn path_in_container_split(&self) -> Vec<&str> {
2468        self.path.split('/').collect()
2469    }
2470
2471    /// This function the *table_name* of this file (the folder that contains this file) if this file is a DB table.
2472    ///
2473    /// It returns None of the file provided is not a DB Table.
2474    pub fn db_table_name_from_path(&self) -> Option<&str> {
2475        let split_path = self.path.split('/').collect::<Vec<_>>();
2476        let start_lower = split_path[0].to_lowercase();
2477        if split_path.len() == 3 && (start_lower == "db" || start_lower == "ceo_db") {
2478            Some(split_path[1])
2479        } else {
2480            None
2481        }
2482    }
2483
2484    /// This function sets the [ContainerPath] of the provided RFile to the provided path..
2485    pub fn set_path_in_container_raw(&mut self, path: &str) {
2486        self.path = path.to_owned();
2487    }
2488
2489    /// This function returns if the RFile can be compressed or not.
2490    pub fn is_compressible(&self, game_info: &GameInfo) -> bool {
2491        !game_info.compression_formats_supported().is_empty() &&
2492
2493        // These files are needed in plain text for this lib to read them.
2494        self.file_name() != Some(RESERVED_NAME_DEPENDENCIES_MANAGER_V2) &&
2495        self.file_name() != Some(RESERVED_NAME_DEPENDENCIES_MANAGER) &&
2496        self.file_name() != Some(RESERVED_NAME_SETTINGS) &&
2497        self.file_name() != Some(RESERVED_NAME_NOTES) &&
2498
2499        // These files either do not benefit from compression (video), may cause issues due to compression (audio not working)
2500        // or may cause overhead due to decompression (models).
2501        !matches!(self.file_type, FileType::Audio | FileType::Dat | FileType::RigidModel | FileType::SoundBank | FileType::Video) &&
2502
2503        // We can only compress files if the game supports them. And only in WH3 (and newer games?) is the table compression bug fixed.
2504        (
2505            !matches!(self.file_type, FileType::DB | FileType::Loc) || (
2506                game_info.key() != KEY_PHARAOH_DYNASTIES &&
2507                game_info.key() != KEY_PHARAOH &&
2508                game_info.key() != KEY_TROY &&
2509                game_info.key() != KEY_THREE_KINGDOMS &&
2510                game_info.key() != KEY_WARHAMMER_2
2511            )
2512        )
2513    }
2514
2515    /// This function guesses the [`FileType`] of the provided RFile and stores it on it for later queries.
2516    ///
2517    /// The way it works is: first it tries to guess it by extension (fast), then by full path (not as fast), then by data (slow and it may fail on lazy-loaded files).
2518    ///
2519    /// This may fail for some files, so if you doubt set the type manually.
2520    pub fn guess_file_type(&mut self) -> Result<()> {
2521
2522        // First, try with extensions.
2523        let path = self.path.to_lowercase();
2524
2525        if path.ends_with(pack::EXTENSION) {
2526            self.file_type = FileType::Pack;
2527        }
2528
2529        else if path.ends_with(loc::EXTENSION) {
2530            self.file_type = FileType::Loc;
2531        }
2532
2533        else if path.ends_with(rigidmodel::EXTENSION) {
2534            self.file_type = FileType::RigidModel
2535        }
2536
2537        else if path.ends_with(animpack::EXTENSION) {
2538            self.file_type = FileType::AnimPack
2539        }
2540
2541        else if path.ends_with(anim::EXTENSION) {
2542            self.file_type = FileType::Anim
2543        }
2544
2545        else if path.ends_with(video::EXTENSION) {
2546            self.file_type = FileType::Video;
2547        }
2548
2549        else if path.ends_with(dat::EXTENSION) {
2550            self.file_type = FileType::Dat;
2551        }
2552
2553        else if path.ends_with(font::EXTENSION) {
2554            self.file_type = FileType::Font;
2555        }
2556
2557        else if audio::EXTENSIONS.iter().any(|x| path.ends_with(x)) {
2558            self.file_type = FileType::Audio;
2559        }
2560
2561        // TODO: detect bin files for maps and tile maps.
2562        else if bmd::EXTENSIONS.iter().any(|x| path.ends_with(x)) {
2563            self.file_type = FileType::BMD;
2564        }
2565
2566        else if bmd_vegetation::EXTENSIONS.iter().any(|x| path.ends_with(x)) {
2567            self.file_type = FileType::BMDVegetation;
2568        }
2569
2570        else if path.ends_with(sound_bank::EXTENSION) {
2571            self.file_type = FileType::SoundBank;
2572        }
2573
2574        else if image::EXTENSIONS.iter().any(|x| path.ends_with(x)) {
2575            self.file_type = FileType::Image;
2576        }
2577
2578        else if cfg!(feature = "support_uic") && path.starts_with(uic::BASE_PATH) && uic::EXTENSIONS.iter().any(|x| path.ends_with(x) || !path.contains('.')) {
2579            self.file_type = FileType::UIC;
2580        }
2581
2582        else if path.ends_with(text::EXTENSION_VMD.0) {
2583            self.file_type = FileType::VMD;
2584        }
2585
2586        else if path.ends_with(text::EXTENSION_WSMODEL.0) {
2587            self.file_type = FileType::WSModel;
2588        }
2589
2590        else if text::EXTENSIONS.iter().any(|(x, _)| path.ends_with(x)) {
2591            self.file_type = FileType::Text;
2592        }
2593
2594        else if path.ends_with(unit_variant::EXTENSION) {
2595            self.file_type = FileType::UnitVariant
2596        }
2597
2598        else if path == group_formations::PATH {
2599            self.file_type = FileType::GroupFormations;
2600        }
2601
2602        else if esf::EXTENSIONS.iter().any(|x| path.ends_with(x)) {
2603            self.file_type = FileType::ESF;
2604        }
2605
2606        // If that failed, try types that need to be in a specific path.
2607        else if matched_combat::BASE_PATHS.iter().any(|x| path.starts_with(*x)) && path.ends_with(matched_combat::EXTENSION) {
2608            self.file_type = FileType::MatchedCombat;
2609        }
2610
2611        else if path.starts_with(anims_table::BASE_PATH) && path.ends_with(anims_table::EXTENSION) {
2612            self.file_type = FileType::AnimsTable;
2613        }
2614
2615        else if path.ends_with(anim_fragment_battle::EXTENSION_OLD) || (path.starts_with(anim_fragment_battle::BASE_PATH) && path.contains(anim_fragment_battle::MID_PATH) && path.ends_with(anim_fragment_battle::EXTENSION_NEW)) {
2616            self.file_type = FileType::AnimFragmentBattle;
2617        }
2618
2619        // If that failed, check if it's in a folder which is known to only have specific files.
2620        // Microoptimization: check the path before using the regex. Regex is very, VERY slow.
2621        else if (path.starts_with("db/") && REGEX_DB.is_match(&path)) || (path.starts_with("ceo_db/") && REGEX_CEO_DB.is_match(&path))  {
2622            self.file_type = FileType::DB;
2623        }
2624
2625        else if path.ends_with(portrait_settings::EXTENSION) && REGEX_PORTRAIT_SETTINGS.is_match(&path) {
2626            self.file_type = FileType::PortraitSettings;
2627        }
2628
2629        else if path.ends_with(atlas::EXTENSION) {
2630            self.file_type = FileType::Atlas;
2631        }
2632
2633        else if path.ends_with(hlsl_compiled::EXTENSION) {
2634            self.file_type = FileType::HlslCompiled;
2635        }
2636
2637        // If we reach this... we're clueless. Leave it unknown.
2638        else {
2639            self.file_type = FileType::Unknown;
2640        }
2641
2642        Ok(())
2643    }
2644
2645    /// This function allows to import a TSV file on the provided Path into a binary database file.
2646    ///
2647    /// It requires the path on disk of the TSV file and the Schema to use. Schema is only needed for DB tables.
2648    pub fn tsv_import_from_path(path: &Path, schema: &Option<Schema>) -> Result<Self> {
2649
2650        // We want the reader to have no quotes, tab as delimiter and custom headers, because otherwise
2651        // Excel, Libreoffice and all the programs that edit this kind of files break them on save.
2652        let mut reader = ReaderBuilder::new()
2653            .delimiter(b'\t')
2654            .quoting(false)
2655            .has_headers(true)
2656            .flexible(true)
2657            .from_path(path)?;
2658
2659        // If we successfully load the TSV file into a reader, check the first line to get the column list and order.
2660        let field_order = reader.headers()?
2661            .iter()
2662            .enumerate()
2663            .map(|(x, y)| (x as u32, y.to_owned()))
2664            .collect::<HashMap<u32, String>>();
2665
2666        // Get the record iterator so we can check the metadata from the second row.
2667        let mut records = reader.records();
2668        let (table_type, table_version, file_path) = match records.next() {
2669            Some(Ok(record)) => {
2670                let metadata = match record.get(0) {
2671                    Some(metadata) => metadata.split(';').map(|x| x.to_owned()).collect::<Vec<String>>(),
2672                    None => return Err(RLibError::ImportTSVWrongTypeTable),
2673                };
2674
2675                let table_type = match metadata.first() {
2676                    Some(table_type) => {
2677                        let mut table_type = table_type.to_owned();
2678                        if table_type.starts_with('#') {
2679                            table_type.remove(0);
2680                        }
2681                        table_type
2682                    },
2683                    None => return Err(RLibError::ImportTSVWrongTypeTable),
2684                };
2685
2686                let table_version = match metadata.get(1) {
2687                    Some(table_version) => table_version.parse::<i32>().map_err(|_| RLibError::ImportTSVInvalidVersion)?,
2688                    None => return Err(RLibError::ImportTSVInvalidVersion),
2689                };
2690
2691                let file_path = match metadata.get(2) {
2692                    Some(file_path) => file_path.replace('\\', "/"),
2693                    None => return Err(RLibError::ImportTSVInvalidOrMissingPath),
2694                };
2695
2696                (table_type, table_version, file_path)
2697            }
2698            Some(Err(_)) |
2699            None => return Err(RLibError::ImportTSVIncorrectRow(1, 0)),
2700        };
2701
2702        // Once we get the metadata, we know what kind of file we have. Create it and pass the records.
2703        let decoded = match &*table_type {
2704            loc::TSV_NAME_LOC | loc::TSV_NAME_LOC_OLD => {
2705                let decoded = Loc::tsv_import(records, &field_order)?;
2706                RFileDecoded::Loc(decoded)
2707            }
2708
2709            // Any other name is assumed to be a db table.
2710            _ => {
2711                match schema {
2712                    Some(schema) => {
2713                        let decoded = DB::tsv_import(records, &field_order, schema, &table_type, table_version)?;
2714                        RFileDecoded::DB(decoded)
2715                    },
2716                    None => return Err(RLibError::SchemaNotProvided),
2717                }
2718            }
2719        };
2720
2721        let rfile = RFile::new_from_decoded(&decoded, 0, &file_path);
2722        Ok(rfile)
2723    }
2724
2725    /// This function allows to export a RFile into a TSV file on disk.
2726    ///
2727    /// Only supported for DB and Loc files.
2728    pub fn tsv_export_to_path(&mut self, path: &Path, schema: &Schema, keys_first: bool) -> Result<()> {
2729
2730        // Sanitize the path before creating the file
2731        let sanitized_path = sanitize_path(path);
2732
2733        // Make sure the folder actually exists.
2734        let mut folder_path = sanitized_path.to_path_buf();
2735        folder_path.pop();
2736        DirBuilder::new().recursive(true).create(&folder_path)?;
2737
2738        // We want the writer to have no quotes, tab as delimiter and custom headers, because otherwise
2739        // Excel, Libreoffice and all the programs that edit this kind of files break them on save.
2740        let mut writer = WriterBuilder::new()
2741            .delimiter(b'\t')
2742            .quote_style(QuoteStyle::Never)
2743            .has_headers(false)
2744            .flexible(true)
2745            .from_path(&sanitized_path)?;
2746
2747        let mut extra_data = DecodeableExtraData::default();
2748        extra_data.set_schema(Some(schema));
2749
2750        let extra_data = Some(extra_data);
2751
2752        // If it fails in decoding, delete the tsv file.
2753        let file = self.decode(&extra_data, false, true);
2754        if let Err(error) = file {
2755            let _ = std::fs::remove_file(&sanitized_path);
2756            return Err(error);
2757        }
2758
2759        let file = match file?.unwrap() {
2760            RFileDecoded::DB(table) => table.tsv_export(&mut writer, self.path_in_container_raw(), keys_first),
2761            RFileDecoded::Loc(table) => table.tsv_export(&mut writer, self.path_in_container_raw()),
2762            _ => unimplemented!()
2763        };
2764
2765        // If the tsv export failed, delete the tsv file.
2766        if file.is_err() {
2767            let _ = std::fs::remove_file(&sanitized_path);
2768        }
2769
2770        file
2771    }
2772
2773    /// This function tries to merge multiple files into one.
2774    ///
2775    /// All files must be of the same type and said type must support merging.
2776    pub fn merge(sources: &[&Self], path: &str) -> Result<Self> {
2777        if sources.len() <= 1 {
2778            return Err(RLibError::RFileMergeOnlyOneFileProvided);
2779        }
2780
2781        let mut file_types = sources.iter().map(|file| file.file_type()).collect::<Vec<_>>();
2782        file_types.sort();
2783        file_types.dedup();
2784
2785        if file_types.len() > 1 {
2786            return Err(RLibError::RFileMergeDifferentTypes);
2787        }
2788
2789        match file_types[0] {
2790            FileType::DB => {
2791                let files = sources.iter().filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some(table) } else { None }).collect::<Vec<_>>();
2792                let data = RFileDecoded::DB(DB::merge(&files)?);
2793                Ok(Self::new_from_decoded(&data, current_time()?, path))
2794            },
2795            FileType::Loc => {
2796                let files = sources.iter().filter_map(|file| if let Ok(RFileDecoded::Loc(table)) = file.decoded() { Some(table) } else { None }).collect::<Vec<_>>();
2797                let data = RFileDecoded::Loc(Loc::merge(&files)?);
2798                Ok(Self::new_from_decoded(&data, current_time()?, path))
2799            },
2800            _ => Err(RLibError::RFileMergeNotSupportedForType(file_types[0].to_string())),
2801        }
2802    }
2803
2804    /// This function tries to update a file to a new version of the same file's format.
2805    ///
2806    /// Files used by this function are expected to be pre-decoded.
2807    pub fn update(&mut self, definition: &Option<Definition>) -> Result<()> {
2808        match self.decoded_mut() {
2809            Ok(RFileDecoded::DB(file)) => match definition {
2810                Some(definition) => file.update(definition),
2811                None => return Err(RLibError::RawTableMissingDefinition),
2812            }
2813            _ => return Err(RLibError::FileNotDecoded(self.path_in_container_raw().to_string())),
2814        }
2815
2816        Ok(())
2817    }
2818
2819    /// This function returns the data hash of the file.
2820    pub fn data_hash(&mut self, extra_data: &Option<EncodeableExtraData>) -> Result<u64> {
2821        Ok(match self.data {
2822            RFileInnerData::Decoded(_) => checksum(CrcAlgorithm::Crc32Iscsi, &self.encode(extra_data, false, false, true)?.unwrap()),
2823            RFileInnerData::Cached(ref data) => checksum(CrcAlgorithm::Crc32Iscsi, data),
2824            RFileInnerData::OnDisk(ref on_disk) => checksum(CrcAlgorithm::Crc32Iscsi, &on_disk.read()?),
2825        })
2826    }
2827
2828    /// Sanitizes a destination path and creates a file with the sanitized path.
2829    /// Logs a message if the filename was changed due to invalid Windows characters.
2830    pub fn sanitize_and_create_file(&mut self, destination_path: &Path, extra_data: &Option<EncodeableExtraData>) -> Result<PathBuf> {
2831        let sanitized_destination_path = sanitize_path(destination_path);
2832
2833        if sanitized_destination_path != destination_path {
2834            warn!("Filename sanitized from '{}' to '{}' due to invalid Windows characters",
2835                  destination_path.to_owned().file_name().unwrap_or_default().to_string_lossy(),
2836                  sanitized_destination_path.file_name().unwrap_or_default().to_string_lossy());
2837        }
2838
2839        // Encode first, then create the destination file. This way, if the source happens to be
2840        // the same path we're writing to (e.g. MyMod Import/Export), we read the source bytes
2841        // before File::create truncates it. Avoids needing to pre-load OnDisk files into memory.
2842        let data = self.encode(extra_data, false, false, true)?.unwrap();
2843        let mut file = BufWriter::new(File::create(&sanitized_destination_path)?);
2844        file.write_all(&data)?;
2845        Ok(sanitized_destination_path)
2846    }
2847
2848    /// Function to encode a file using data from a external path, effectively replacing its data.
2849    /// without replacing the file's metadata.
2850    ///
2851    /// NOTE: If TSV is detected and fails to import, this returns an error.
2852    pub fn encode_from_external_data(&mut self, schema: &Option<Schema>, external_path: &Path) -> Result<()> {
2853
2854        let mut file = BufReader::new(File::open(external_path)?);
2855        let mut data = vec![];
2856        file.read_to_end(&mut data)?;
2857
2858        // If we're dealing with a TSV, make sure to import it before setting up the data.
2859        match external_path.extension() {
2860            Some(extension) => {
2861                if extension.to_string_lossy() == "tsv" {
2862                    match RFile::tsv_import_from_path(external_path, schema) {
2863                        Ok(rfile) => self.set_decoded(rfile.decoded()?.clone())?,
2864                        Err(_) => self.set_cached(&data),
2865                    }
2866                } else {
2867                    self.set_cached(&data);
2868                }
2869            }
2870            None => self.set_cached(&data),
2871        }
2872
2873        // If they're tables, make sure they're left decoded in memory.
2874        if self.file_type() == FileType::DB || self.file_type() == FileType::Loc {
2875            if let Some(ref schema) = schema {
2876                let mut extra_data = DecodeableExtraData::default();
2877                extra_data.set_schema(Some(schema));
2878                let extra_data = Some(extra_data);
2879                let _ = self.decode(&extra_data, true, false);
2880            }
2881        }
2882
2883        Ok(())
2884    }
2885}
2886
2887impl OnDisk {
2888
2889    /// This function tries to read and return the raw data of an RFile.
2890    ///
2891    /// This returns the data uncompressed and unencrypted.
2892    fn read(&self) -> Result<Vec<u8>> {
2893
2894        // Date check, to ensure the source file or container hasn't been modified since we got the indexes to read it.
2895        let mut file = BufReader::new(File::open(&self.path)?);
2896        let timestamp = last_modified_time_from_file(file.get_ref())?;
2897        if timestamp != self.timestamp {
2898            return Err(RLibError::FileSourceChanged(self.path.clone()));
2899        }
2900
2901        // Read the data from disk.
2902        let mut data = vec![0; self.size as usize];
2903        file.seek(SeekFrom::Start(self.start))?;
2904        file.read_exact(&mut data)?;
2905
2906        // If the data is encrypted, decrypt it.
2907        if self.is_encrypted.is_some() {
2908            data = Cursor::new(data).decrypt()?;
2909        }
2910
2911        // If the data is compressed. decompress it.
2912        if self.is_compressed {
2913            data = data.as_slice().decompress()?;
2914        }
2915
2916        Ok(data)
2917    }
2918}
2919
2920impl ContainerPath {
2921
2922    /// This function returns true if the provided [ContainerPath] corresponds to a file.
2923    pub fn is_file(&self) -> bool {
2924        matches!(self, ContainerPath::File(_))
2925    }
2926
2927    /// This function returns true if the provided [ContainerPath] corresponds to a folder.
2928    pub fn is_folder(&self) -> bool {
2929        matches!(self, ContainerPath::Folder(_))
2930    }
2931
2932    /// This function returns true if the provided [ContainerPath] corresponds to a root Pack.
2933    pub fn is_pack(&self) -> bool {
2934        match self {
2935            ContainerPath::Folder(path) => path.is_empty(),
2936            _ => false,
2937        }
2938    }
2939
2940    /// This function returns a reference to the path stored within the provided [ContainerPath].
2941    pub fn path_raw(&self) -> &str {
2942        match self {
2943            Self::File(ref path) => path,
2944            Self::Folder(ref path) => path,
2945        }
2946    }
2947
2948    /// This function returns the last item of the provided [ContainerPath], if any.
2949    pub fn name(&self) -> Option<&str> {
2950        self.path_raw().split('/').next_back()
2951    }
2952
2953    /// This function the *table_name* of this file (the folder that contains this file) if this file is a DB table.
2954    ///
2955    /// It returns None of the file provided is not a DB Table.
2956    pub fn db_table_name_from_path(&self) -> Option<&str> {
2957        let split_path = self.path_raw().split('/').collect::<Vec<_>>();
2958        let start_lower = split_path[0].to_lowercase();
2959        if split_path.len() == 3 && (start_lower == "db" || start_lower == "ceo_db") {
2960            Some(split_path[1])
2961        } else {
2962            None
2963        }
2964    }
2965
2966    /// This function returns the path of the parent folder of the provided [ContainerPath].
2967    ///
2968    /// If the provided [ContainerPath] corresponds to a Container root, the path returned will be the current one.
2969    pub fn parent_path(&self) -> String {
2970        match self {
2971            ContainerPath::File(path) |
2972            ContainerPath::Folder(path) => {
2973                if path.is_empty() || (path.chars().count() == 1 && path.starts_with('/')) {
2974                    path.to_owned()
2975                } else {
2976                    let mut path_split = path.split('/').collect::<Vec<_>>();
2977                    path_split.pop();
2978                    path_split.join("/")
2979                }
2980            },
2981        }
2982    }
2983
2984    /// This function removes collided items from the provided list of [ContainerPath].
2985    ///
2986    /// This means, if you have a file and a folder containing the file, it removes the file.
2987    pub fn dedup(paths: &[Self]) -> Vec<Self> {
2988
2989        // As this operation can get very expensive very fast, we first check if we have a path containing the root of the container.
2990        let root = ContainerPath::Folder("".to_string());
2991        if paths.contains(&root) {
2992            return vec![root; 1];
2993        }
2994
2995        // If we don't have the root of the container, second optimization: check if we have at least one folder.
2996        // If not, we just need to dedup the file list.
2997        if !paths.par_iter().any(|item| matches!(item, ContainerPath::Folder(_))) {
2998            let mut paths = paths.to_vec();
2999            paths.sort();
3000            paths.dedup();
3001            return paths;
3002        }
3003
3004        // If we reached this point, we have a mix of files and folders, or only folders.
3005        // In any case, we need to filter them, then dedup the resultant paths.
3006        let items_to_remove = paths.par_iter().filter(|item_type_to_add| {
3007            match item_type_to_add {
3008
3009                // If it's a file, we have to check if there is a folder containing it.
3010                ContainerPath::File(ref path_to_add) => {
3011                    !paths.par_iter()
3012                        .any(|item_type| {
3013
3014                        // If the other one is a folder that contains it, dont add it.
3015                        item_type.is_folder() && path_to_add.starts_with(item_type.path_raw())
3016                    })
3017                }
3018
3019                // If it's a folder, we have to check if there is already another folder containing it.
3020                ContainerPath::Folder(ref path_to_add) => {
3021                    !paths.par_iter()
3022                        .any(|item_type| {
3023
3024                        // If the other one is a folder that contains it, dont add it.
3025                        let path = item_type.path_raw();
3026                        item_type.is_folder() && path_to_add.starts_with(path) && path_to_add.len() > path.len() && path_to_add.is_char_boundary(path.len()) && path_to_add.as_bytes()[path.len()] == b'/'
3027                    })
3028                }
3029            }
3030        }).cloned().collect::<Vec<Self>>();
3031
3032        let mut paths = paths.to_vec();
3033        paths.retain(|x| items_to_remove.contains(x));
3034        paths.sort();
3035        paths.dedup();
3036        paths
3037    }
3038}
3039
3040impl Ord for ContainerPath {
3041    fn cmp(&self, other: &Self) -> Ordering {
3042        match self {
3043            ContainerPath::File(a) => match other {
3044                ContainerPath::File(b) => a.cmp(b),
3045                ContainerPath::Folder(_) => Ordering::Less,
3046            }
3047            ContainerPath::Folder(a) => match other {
3048                ContainerPath::File(_) => Ordering::Greater,
3049                ContainerPath::Folder(b) => a.cmp(b),
3050            }
3051        }
3052    }
3053}
3054
3055impl PartialOrd for ContainerPath {
3056    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3057        Some(self.cmp(other))
3058    }
3059}
3060
3061impl Display for FileType {
3062    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3063        match self {
3064            FileType::Anim => write!(f, "Anim"),
3065            FileType::AnimFragmentBattle => write!(f, "AnimFragmentBattle"),
3066            FileType::AnimPack => write!(f, "AnimPack"),
3067            FileType::AnimsTable => write!(f, "AnimsTable"),
3068            FileType::Atlas => write!(f, "Atlas"),
3069            FileType::Audio => write!(f, "Audio"),
3070            FileType::BMD => write!(f, "Battle Map Definition"),
3071            FileType::BMDVegetation => write!(f, "Battle Map Definition (Vegetation)"),
3072            FileType::Dat => write!(f, "Dat Audio File"),
3073            FileType::DB => write!(f, "DB Table"),
3074            FileType::ESF => write!(f, "ESF"),
3075            FileType::Font => write!(f, "Font"),
3076            FileType::HlslCompiled => write!(f, "Hlsl Compiled"),
3077            FileType::GroupFormations => write!(f, "Group Formations"),
3078            FileType::Image => write!(f, "Image"),
3079            FileType::Loc => write!(f, "Loc Table"),
3080            FileType::MatchedCombat => write!(f, "Matched Combat"),
3081            FileType::Pack => write!(f, "PackFile"),
3082            FileType::PortraitSettings => write!(f, "Portrait Settings"),
3083            FileType::RigidModel => write!(f, "RigidModel"),
3084            FileType::SoundBank => write!(f, "SoundBank"),
3085            FileType::Text => write!(f, "Text"),
3086            FileType::UIC => write!(f, "UI Component"),
3087            FileType::UnitVariant => write!(f, "Unit Variant"),
3088            FileType::Unknown => write!(f, "Unknown"),
3089            FileType::Video => write!(f, "Video"),
3090            FileType::VMD => write!(f, "VMD"),
3091            FileType::WSModel => write!(f, "WSModel"),
3092        }
3093    }
3094}
3095
3096impl From<&str> for FileType {
3097    fn from(value: &str) -> Self {
3098        match value {
3099            "Anim" => FileType::Anim,
3100            "AnimFragmentBattle" => FileType::AnimFragmentBattle,
3101            "AnimPack" => FileType::AnimPack,
3102            "AnimsTable" => FileType::AnimsTable,
3103            "Atlas" => FileType::Atlas,
3104            "Audio" => FileType::Audio,
3105            "BMD" => FileType::BMD,
3106            "BMDVegetation" => FileType::BMDVegetation,
3107            "Dat" => FileType::Dat,
3108            "DB" => FileType::DB,
3109            "ESF" => FileType::ESF,
3110            "Font" => FileType::Font,
3111            "HlslCompiled" => FileType::HlslCompiled,
3112            "GroupFormations" => FileType::GroupFormations,
3113            "Image" => FileType::Image,
3114            "Loc" => FileType::Loc,
3115            "MatchedCombat" => FileType::MatchedCombat,
3116            "Pack" => FileType::Pack,
3117            "PortraitSettings" => FileType::PortraitSettings,
3118            "RigidModel" => FileType::RigidModel,
3119            "SoundBank" => FileType::SoundBank,
3120            "Text" => FileType::Text,
3121            "UIC" => FileType::UIC,
3122            "UnitVariant" => FileType::UnitVariant,
3123            "Unknown" => FileType::Unknown,
3124            "Video" => FileType::Video,
3125            "VMD" => FileType::VMD,
3126            "WSModel" => FileType::WSModel,
3127            _ => unimplemented!(),
3128        }
3129    }
3130}
3131
3132impl From<FileType> for String {
3133    fn from(value: FileType) -> String {
3134        match value {
3135            FileType::Anim => "Anim",
3136            FileType::AnimFragmentBattle => "AnimFragmentBattle",
3137            FileType::AnimPack => "AnimPack",
3138            FileType::AnimsTable => "AnimsTable",
3139            FileType::Atlas => "Atlas",
3140            FileType::Audio => "Audio",
3141            FileType::BMD => "BMD",
3142            FileType::BMDVegetation => "BMD Vegetation",
3143            FileType::Dat => "Dat",
3144            FileType::DB => "DB",
3145            FileType::ESF => "ESF",
3146            FileType::Font => "Font",
3147            FileType::HlslCompiled => "HlslCompiled",
3148            FileType::GroupFormations => "GroupFormations",
3149            FileType::Image => "Image",
3150            FileType::Loc => "Loc",
3151            FileType::MatchedCombat => "MatchedCombat",
3152            FileType::Pack => "Pack",
3153            FileType::PortraitSettings => "PortraitSettings",
3154            FileType::RigidModel => "RigidModel",
3155            FileType::SoundBank => "SoundBank",
3156            FileType::Text => "Text",
3157            FileType::UIC => "UIC",
3158            FileType::UnitVariant => "UnitVariant",
3159            FileType::Unknown => "Unknown",
3160            FileType::Video => "Video",
3161            FileType::VMD => "VMD",
3162            FileType::WSModel => "WSModel",
3163        }.to_owned()
3164    }
3165}
3166
3167impl From<&RFileDecoded> for FileType {
3168    fn from(file: &RFileDecoded) -> Self {
3169        match file {
3170            RFileDecoded::Anim(_) => Self::Anim,
3171            RFileDecoded::AnimFragmentBattle(_) => Self::AnimFragmentBattle,
3172            RFileDecoded::AnimPack(_) => Self::AnimPack,
3173            RFileDecoded::AnimsTable(_) => Self::AnimsTable,
3174            RFileDecoded::Atlas(_) => Self::Atlas,
3175            RFileDecoded::Audio(_) => Self::Audio,
3176            RFileDecoded::BMD(_) => Self::BMD,
3177            RFileDecoded::BMDVegetation(_) => Self::BMDVegetation,
3178            RFileDecoded::Dat(_) => Self::Dat,
3179            RFileDecoded::DB(_) => Self::DB,
3180            RFileDecoded::ESF(_) => Self::ESF,
3181            RFileDecoded::Font(_) => Self::Font,
3182            RFileDecoded::HlslCompiled(_) => Self::HlslCompiled,
3183            RFileDecoded::GroupFormations(_) => Self::GroupFormations,
3184            RFileDecoded::Image(_) => Self::Image,
3185            RFileDecoded::Loc(_) => Self::Loc,
3186            RFileDecoded::MatchedCombat(_) => Self::MatchedCombat,
3187            RFileDecoded::Pack(_) => Self::Pack,
3188            RFileDecoded::PortraitSettings(_) => Self::PortraitSettings,
3189            RFileDecoded::RigidModel(_) => Self::RigidModel,
3190            RFileDecoded::SoundBank(_) => Self::SoundBank,
3191            RFileDecoded::Text(_) => Self::Text,
3192            RFileDecoded::UIC(_) => Self::UIC,
3193            RFileDecoded::UnitVariant(_) => Self::UnitVariant,
3194            RFileDecoded::Unknown(_) => Self::Unknown,
3195            RFileDecoded::Video(_) => Self::Video,
3196            RFileDecoded::VMD(_) => Self::VMD,
3197            RFileDecoded::WSModel(_) => Self::WSModel,
3198        }
3199    }
3200}
3201
3202impl<'a> EncodeableExtraData<'a> {
3203
3204    /// This functions generates an EncodeableExtraData for a specific game.
3205    pub fn new_from_game_info(game_info: &'a GameInfo) -> Self {
3206        let mut extra_data = Self::default();
3207        extra_data.set_game_info(Some(game_info));
3208        extra_data.set_table_has_guid(*game_info.db_tables_have_guid());
3209        extra_data
3210    }
3211
3212    /// This functions generates an EncodeableExtraData for a specific game and settings.
3213    pub fn new_from_game_info_and_settings(game_info: &'a GameInfo, cf: CompressionFormat, disable_regen_table_guid: bool) -> EncodeableExtraData<'a> {
3214        let mut extra_data = EncodeableExtraData::new_from_game_info(game_info);
3215        extra_data.set_regenerate_table_guid(!disable_regen_table_guid);
3216        extra_data.set_compression_format(cf);
3217        extra_data
3218    }
3219}