Skip to main content

rpfm_lib/games/
pfh_version.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//! PackFile format version identification.
12//!
13//! This module defines the different PackFile format versions used across Total War games.
14//! Each version represents a different internal format with varying capabilities and structure.
15//!
16//! # PackFile Versions
17//!
18//! Total War games have evolved their PackFile format over time:
19//!
20//! - **PFH6**: Troy (v1.3.0+)
21//! - **PFH5**: Warhammer 2, Warhammer 3, Three Kingdoms, Troy (pre-1.3.0), Pharaoh, Pharaoh Dynasties, Arena
22//! - **PFH4**: Warhammer 1, Attila, Rome 2, Thrones of Britannia
23//! - **PFH3**: Shogun 2 (post-patch 15)
24//! - **PFH2**: Shogun 2 (pre-patch 15, before Fall of the Samurai expansion)
25//! - **PFH0**: Napoleon, Empire
26//!
27//! # Version Identification
28//!
29//! PackFiles are identified by a 4-byte "preamble" or "magic number" at the start:
30//! ```text
31//! Offset 0x00: "PFH6" or "PFH5" or "PFH4" or "PFH3" or "PFH2" or "PFH0"
32//! ```
33//!
34//! # Format Differences
35//!
36//! Different versions support different features:
37//! - Compression algorithms (newer versions support more formats)
38//! - Header structure and metadata
39//! - File entry format
40//! - Timestamps
41//! - File path encoding
42//!
43//! # Usage
44//!
45//! ```ignore
46//! use rpfm_lib::games::pfh_version::PFHVersion;
47//!
48//! // Get version from preamble string
49//! let version = PFHVersion::version("PFH5").unwrap();
50//! assert_eq!(version, PFHVersion::PFH5);
51//!
52//! // Get preamble string from version
53//! assert_eq!(version.value(), "PFH5");
54//! ```
55
56use std::{fmt, fmt::Display};
57use serde_derive::{Serialize, Deserialize};
58
59use crate::error::{RLibError, Result};
60
61/// Preamble string for PFH6 format
62const PFH6_PREAMBLE: &str = "PFH6";
63/// Preamble string for PFH5 format
64const PFH5_PREAMBLE: &str = "PFH5";
65/// Preamble string for PFH4 format
66const PFH4_PREAMBLE: &str = "PFH4";
67/// Preamble string for PFH3 format
68const PFH3_PREAMBLE: &str = "PFH3";
69/// Preamble string for PFH2 format
70const PFH2_PREAMBLE: &str = "PFH2";
71/// Preamble string for PFH0 format
72const PFH0_PREAMBLE: &str = "PFH0";
73
74//-------------------------------------------------------------------------------//
75//                              Enums & Structs
76//-------------------------------------------------------------------------------//
77
78/// PackFile format version.
79///
80/// Identifies the internal format version of a PackFile, which determines its
81/// structure, capabilities, and which games can read it.
82///
83/// # Version History
84///
85/// Each variant represents a different format evolution in Total War's PackFile system.
86/// Newer versions generally support more features but may not be readable by older games.
87///
88/// # Compatibility
89///
90/// Games can typically read their own version and sometimes older versions, but cannot
91/// read newer versions. For maximum compatibility when creating mods, use the version
92/// matching the target game.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94pub enum PFHVersion {
95
96    /// Used in Troy (v1.3.0+).
97    PFH6,
98
99    /// Used in Warhammer 2, Warhammer 3, Three Kingdoms, Troy (pre-1.3.0), Pharaoh, Pharaoh Dynasties, Arena.
100    PFH5,
101
102    /// Used in Warhammer 1, Attila, Rome 2, Thrones of Britannia.
103    PFH4,
104
105    /// Used in Shogun 2.
106    PFH3,
107
108    /// Used in Shogun 2 before patch 15 (Fall of the Samurai expansion).
109    PFH2,
110
111    /// Used in Napoleon and Empire.
112    PFH0
113}
114
115//-------------------------------------------------------------------------------//
116//                             Implementations
117//-------------------------------------------------------------------------------//
118
119/// Implementation of `PFHVersion`.
120impl PFHVersion {
121
122    /// Returns the 4-byte preamble string for this format version.
123    ///
124    /// This is the "magic number" that appears at offset 0x00 in the PackFile header
125    /// to identify its format version.
126    ///
127    /// # Returns
128    ///
129    /// A 4-character string like `"PFH6"`, `"PFH5"`, etc.
130    ///
131    /// # Example
132    ///
133    /// ```ignore
134    /// use rpfm_lib::games::pfh_version::PFHVersion;
135    ///
136    /// assert_eq!(PFHVersion::PFH6.value(), "PFH6");
137    /// assert_eq!(PFHVersion::PFH5.value(), "PFH5");
138    /// assert_eq!(PFHVersion::PFH0.value(), "PFH0");
139    /// ```
140    pub fn value(&self) -> &str {
141        match *self {
142            PFHVersion::PFH6 => PFH6_PREAMBLE,
143            PFHVersion::PFH5 => PFH5_PREAMBLE,
144            PFHVersion::PFH4 => PFH4_PREAMBLE,
145            PFHVersion::PFH3 => PFH3_PREAMBLE,
146            PFHVersion::PFH2 => PFH2_PREAMBLE,
147            PFHVersion::PFH0 => PFH0_PREAMBLE,
148        }
149    }
150
151    /// Parses a preamble string into a format version.
152    ///
153    /// Converts a 4-byte preamble read from a PackFile header into the corresponding
154    /// [`PFHVersion`] enum variant.
155    ///
156    /// # Arguments
157    ///
158    /// * `value` - The 4-character preamble string (e.g., `"PFH5"`)
159    ///
160    /// # Returns
161    ///
162    /// Returns the matching [`PFHVersion`], or an error if the preamble is not recognized.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`RLibError::UnknownPFHVersion`] if the preamble doesn't match any known version.
167    ///
168    /// # Example
169    ///
170    /// ```ignore
171    /// use rpfm_lib::games::pfh_version::PFHVersion;
172    ///
173    /// let version = PFHVersion::version("PFH5").unwrap();
174    /// assert_eq!(version, PFHVersion::PFH5);
175    ///
176    /// // Invalid preamble returns error
177    /// assert!(PFHVersion::version("PFH9").is_err());
178    /// ```
179    pub fn version(value: &str) -> Result<Self> {
180        match value {
181            PFH6_PREAMBLE => Ok(PFHVersion::PFH6),
182            PFH5_PREAMBLE => Ok(PFHVersion::PFH5),
183            PFH4_PREAMBLE => Ok(PFHVersion::PFH4),
184            PFH3_PREAMBLE => Ok(PFHVersion::PFH3),
185            PFH2_PREAMBLE => Ok(PFHVersion::PFH2),
186            PFH0_PREAMBLE => Ok(PFHVersion::PFH0),
187            _ => Err(RLibError::UnknownPFHVersion(value.to_owned())),
188        }
189    }
190}
191
192/// Display implementation of `PFHVersion`.
193impl Display for PFHVersion {
194    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195        match self {
196            PFHVersion::PFH6 => write!(f, "PFH6"),
197            PFHVersion::PFH5 => write!(f, "PFH5"),
198            PFHVersion::PFH4 => write!(f, "PFH4"),
199            PFHVersion::PFH3 => write!(f, "PFH3"),
200            PFHVersion::PFH2 => write!(f, "PFH2"),
201            PFHVersion::PFH0 => write!(f, "PFH0"),
202        }
203    }
204}
205
206/// Implementation of trait `Default` for `PFHVersion`.
207impl Default for PFHVersion {
208    fn default() -> Self {
209        Self::PFH6
210    }
211}