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}