1use crate::error::Result;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub enum M2Version {
6 Classic,
8
9 TBC,
11
12 WotLK,
14
15 Cataclysm,
17
18 MoP,
20
21 WoD,
23
24 Legion,
26
27 BfA,
29
30 Shadowlands,
32
33 Dragonflight,
35
36 TheWarWithin,
38}
39
40impl M2Version {
41 pub fn from_string(s: &str) -> Result<Self> {
43 let parts: Vec<&str> = s.split('.').collect();
44 if parts.is_empty() {
45 return Err(crate::error::M2Error::UnsupportedVersion(format!(
46 "Invalid version string: {s}"
47 )));
48 }
49
50 let major = parts[0].parse::<u32>().map_err(|_| {
51 crate::error::M2Error::UnsupportedVersion(format!(
52 "Invalid major version: {}",
53 parts[0]
54 ))
55 })?;
56
57 Ok(match major {
58 1 => M2Version::Classic,
59 2 => M2Version::TBC,
60 3 => M2Version::WotLK,
61 4 => M2Version::Cataclysm,
62 5 => M2Version::MoP,
63 6 => M2Version::WoD,
64 7 => M2Version::Legion,
65 8 => M2Version::BfA,
66 9 => M2Version::Shadowlands,
67 10 => M2Version::Dragonflight,
68 11 => M2Version::TheWarWithin,
69 _ => {
70 return Err(crate::error::M2Error::UnsupportedVersion(format!(
71 "Unknown WoW version: {major}"
72 )));
73 }
74 })
75 }
76
77 pub fn from_expansion_name(s: &str) -> Result<Self> {
80 match s.to_lowercase().as_str() {
82 "vanilla" | "classic" => Ok(M2Version::Classic),
83 "tbc" | "bc" | "burningcrusade" | "burning_crusade" => Ok(M2Version::TBC),
84 "wotlk" | "wrath" | "lichking" | "lich_king" | "wlk" => Ok(M2Version::WotLK),
85 "cata" | "cataclysm" => Ok(M2Version::Cataclysm),
86 "mop" | "pandaria" | "mists" | "mists_of_pandaria" => Ok(M2Version::MoP),
87 "wod" | "draenor" | "warlords" | "warlords_of_draenor" => Ok(M2Version::WoD),
88 "legion" => Ok(M2Version::Legion),
89 "bfa" | "bfazeroth" | "battle_for_azeroth" | "battleforazeroth" => Ok(M2Version::BfA),
90 "sl" | "shadowlands" => Ok(M2Version::Shadowlands),
91 "df" | "dragonflight" => Ok(M2Version::Dragonflight),
92 "tww" | "warwithin" | "the_war_within" | "thewarwithin" => Ok(M2Version::TheWarWithin),
93 _ => {
94 Self::from_string(s)
96 }
97 }
98 }
99
100 pub fn from_header_version(version: u32) -> Option<Self> {
103 match version {
104 256 => Some(Self::Classic), 260 => Some(Self::TBC), 264 => Some(Self::WotLK), 272 => Some(Self::Cataclysm), 257..=259 => Some(Self::Classic),
112 261..=263 => Some(Self::TBC),
113 265..=271 => Some(Self::WotLK),
114
115 8 => Some(Self::MoP),
118 10 => Some(Self::WoD),
119 11 => Some(Self::Legion),
120 16 => Some(Self::BfA),
121 17 => Some(Self::Shadowlands),
122 18 => Some(Self::Dragonflight),
123 19 => Some(Self::TheWarWithin),
124
125 _ => None,
126 }
127 }
128
129 pub fn to_header_version(&self) -> u32 {
132 match self {
133 Self::Classic => 256, Self::TBC => 260, Self::WotLK => 264, Self::Cataclysm => 272, Self::MoP => 272, Self::WoD => 10,
142 Self::Legion => 11,
143 Self::BfA => 16,
144 Self::Shadowlands => 17,
145 Self::Dragonflight => 18,
146 Self::TheWarWithin => 19,
147 }
148 }
149
150 pub fn expansion_name(&self) -> &'static str {
152 match self {
153 Self::Classic => "Classic",
154 Self::TBC => "The Burning Crusade",
155 Self::WotLK => "Wrath of the Lich King",
156 Self::Cataclysm => "Cataclysm",
157 Self::MoP => "Mists of Pandaria",
158 Self::WoD => "Warlords of Draenor",
159 Self::Legion => "Legion",
160 Self::BfA => "Battle for Azeroth",
161 Self::Shadowlands => "Shadowlands",
162 Self::Dragonflight => "Dragonflight",
163 Self::TheWarWithin => "The War Within",
164 }
165 }
166
167 pub fn to_version_string(&self) -> &'static str {
169 match self {
170 Self::Classic => "1.12.1",
171 Self::TBC => "2.4.3",
172 Self::WotLK => "3.3.5a",
173 Self::Cataclysm => "4.3.4",
174 Self::MoP => "5.4.8",
175 Self::WoD => "6.2.4",
176 Self::Legion => "7.3.5",
177 Self::BfA => "8.3.7",
178 Self::Shadowlands => "9.2.7",
179 Self::Dragonflight => "10.2.0",
180 Self::TheWarWithin => "11.0.0",
181 }
182 }
183
184 pub fn has_direct_conversion_path(&self, target: &Self) -> bool {
186 let self_ord = *self as usize;
188 let target_ord = *target as usize;
189
190 (self_ord as isize - target_ord as isize).abs() == 1
191 }
192
193 pub fn supports_chunked_format(&self) -> bool {
197 match self {
198 Self::Classic | Self::TBC => false,
199 Self::WotLK | Self::Cataclysm | Self::MoP => true, _ => true, }
202 }
203
204 pub fn uses_external_chunks(&self) -> bool {
208 match self {
209 Self::Classic | Self::TBC | Self::WotLK | Self::Cataclysm | Self::MoP => false,
210 _ => false, }
212 }
213
214 pub fn uses_inline_data(&self) -> bool {
217 match self {
218 Self::Classic | Self::TBC | Self::WotLK | Self::Cataclysm | Self::MoP => true,
219 _ => true, }
221 }
222
223 pub fn empirical_version_number(&self) -> Option<u32> {
226 match self {
227 Self::Classic => Some(256),
228 Self::TBC => Some(260),
229 Self::WotLK => Some(264),
230 Self::Cataclysm => Some(272),
231 Self::MoP => Some(272),
232 _ => None, }
234 }
235}
236
237impl std::fmt::Display for M2Version {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 write!(
240 f,
241 "{} ({})",
242 self.expansion_name(),
243 self.to_version_string()
244 )
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_version_from_string() {
254 assert_eq!(
255 M2Version::from_string("1.12.1").unwrap(),
256 M2Version::Classic
257 );
258 assert_eq!(M2Version::from_string("2.4.3").unwrap(), M2Version::TBC);
259 assert_eq!(M2Version::from_string("3.3.5a").unwrap(), M2Version::WotLK);
260 assert_eq!(
261 M2Version::from_string("4.3.4").unwrap(),
262 M2Version::Cataclysm
263 );
264 assert_eq!(M2Version::from_string("5.4.8").unwrap(), M2Version::MoP);
265 }
266
267 #[test]
268 fn test_version_from_expansion_name() {
269 assert_eq!(
270 M2Version::from_expansion_name("classic").unwrap(),
271 M2Version::Classic
272 );
273 assert_eq!(
274 M2Version::from_expansion_name("TBC").unwrap(),
275 M2Version::TBC
276 );
277 assert_eq!(
278 M2Version::from_expansion_name("wotlk").unwrap(),
279 M2Version::WotLK
280 );
281 assert_eq!(
282 M2Version::from_expansion_name("cata").unwrap(),
283 M2Version::Cataclysm
284 );
285 assert_eq!(
286 M2Version::from_expansion_name("MoP").unwrap(),
287 M2Version::MoP
288 );
289
290 assert_eq!(
292 M2Version::from_expansion_name("3.3.5a").unwrap(),
293 M2Version::WotLK
294 );
295 }
296
297 #[test]
298 fn test_header_version_conversion() {
299 assert_eq!(
301 M2Version::from_header_version(256),
302 Some(M2Version::Classic)
303 );
304 assert_eq!(M2Version::from_header_version(260), Some(M2Version::TBC));
305 assert_eq!(M2Version::from_header_version(264), Some(M2Version::WotLK));
306 assert_eq!(
307 M2Version::from_header_version(272),
308 Some(M2Version::Cataclysm)
309 );
310
311 assert_eq!(
313 M2Version::from_header_version(257),
314 Some(M2Version::Classic)
315 );
316 assert_eq!(M2Version::from_header_version(261), Some(M2Version::TBC));
317 assert_eq!(M2Version::from_header_version(265), Some(M2Version::WotLK));
318
319 assert_eq!(M2Version::from_header_version(8), Some(M2Version::MoP));
321 assert_eq!(M2Version::from_header_version(10), Some(M2Version::WoD));
322 assert_eq!(M2Version::from_header_version(11), Some(M2Version::Legion));
323 assert_eq!(M2Version::from_header_version(16), Some(M2Version::BfA));
324 assert_eq!(
325 M2Version::from_header_version(17),
326 Some(M2Version::Shadowlands)
327 );
328 assert_eq!(
329 M2Version::from_header_version(18),
330 Some(M2Version::Dragonflight)
331 );
332 assert_eq!(
333 M2Version::from_header_version(19),
334 Some(M2Version::TheWarWithin)
335 );
336
337 assert_eq!(M2Version::from_header_version(1), None);
339 assert_eq!(M2Version::from_header_version(5), None);
340 assert_eq!(M2Version::from_header_version(273), None);
341 }
342
343 #[test]
344 fn test_conversion_paths() {
345 assert!(M2Version::Classic.has_direct_conversion_path(&M2Version::TBC));
346 assert!(M2Version::TBC.has_direct_conversion_path(&M2Version::WotLK));
347 assert!(M2Version::WotLK.has_direct_conversion_path(&M2Version::Cataclysm));
348 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::MoP));
349 assert!(!M2Version::Classic.has_direct_conversion_path(&M2Version::TheWarWithin));
350 }
351
352 #[test]
353 fn test_empirical_version_features() {
354 assert!(!M2Version::Classic.supports_chunked_format());
356 assert!(!M2Version::TBC.supports_chunked_format());
357 assert!(M2Version::WotLK.supports_chunked_format());
358 assert!(M2Version::Cataclysm.supports_chunked_format());
359 assert!(M2Version::MoP.supports_chunked_format());
360
361 assert!(!M2Version::Classic.uses_external_chunks());
363 assert!(!M2Version::TBC.uses_external_chunks());
364 assert!(!M2Version::WotLK.uses_external_chunks());
365 assert!(!M2Version::Cataclysm.uses_external_chunks());
366 assert!(!M2Version::MoP.uses_external_chunks());
367
368 assert!(M2Version::Classic.uses_inline_data());
370 assert!(M2Version::TBC.uses_inline_data());
371 assert!(M2Version::WotLK.uses_inline_data());
372 assert!(M2Version::Cataclysm.uses_inline_data());
373 assert!(M2Version::MoP.uses_inline_data());
374 }
375
376 #[test]
377 fn test_empirical_version_numbers() {
378 assert_eq!(M2Version::Classic.empirical_version_number(), Some(256));
379 assert_eq!(M2Version::TBC.empirical_version_number(), Some(260));
380 assert_eq!(M2Version::WotLK.empirical_version_number(), Some(264));
381 assert_eq!(M2Version::Cataclysm.empirical_version_number(), Some(272));
382 assert_eq!(M2Version::MoP.empirical_version_number(), Some(272));
383
384 assert_eq!(M2Version::WoD.empirical_version_number(), None);
386 assert_eq!(M2Version::Legion.empirical_version_number(), None);
387 }
388
389 #[test]
390 fn test_header_version_roundtrip() {
391 assert_eq!(M2Version::Classic.to_header_version(), 256);
393 assert_eq!(M2Version::TBC.to_header_version(), 260);
394 assert_eq!(M2Version::WotLK.to_header_version(), 264);
395 assert_eq!(M2Version::Cataclysm.to_header_version(), 272);
396 assert_eq!(M2Version::MoP.to_header_version(), 272);
397
398 assert_eq!(
400 M2Version::from_header_version(M2Version::Classic.to_header_version()),
401 Some(M2Version::Classic)
402 );
403 assert_eq!(
404 M2Version::from_header_version(M2Version::TBC.to_header_version()),
405 Some(M2Version::TBC)
406 );
407 assert_eq!(
408 M2Version::from_header_version(M2Version::WotLK.to_header_version()),
409 Some(M2Version::WotLK)
410 );
411 assert_eq!(
412 M2Version::from_header_version(M2Version::Cataclysm.to_header_version()),
413 Some(M2Version::Cataclysm)
414 );
415 }
416}