use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum M2Version {
Vanilla,
TBC,
WotLK,
Cataclysm,
MoP,
WoD,
Legion,
BfA,
Shadowlands,
Dragonflight,
TheWarWithin,
}
impl M2Version {
pub fn from_string(s: &str) -> Result<Self> {
let parts: Vec<&str> = s.split('.').collect();
if parts.is_empty() {
return Err(crate::error::M2Error::UnsupportedVersion(format!(
"Invalid version string: {s}"
)));
}
let major = parts[0].parse::<u32>().map_err(|_| {
crate::error::M2Error::UnsupportedVersion(format!(
"Invalid major version: {}",
parts[0]
))
})?;
Ok(match major {
1 => M2Version::Vanilla,
2 => M2Version::TBC,
3 => M2Version::WotLK,
4 => M2Version::Cataclysm,
5 => M2Version::MoP,
6 => M2Version::WoD,
7 => M2Version::Legion,
8 => M2Version::BfA,
9 => M2Version::Shadowlands,
10 => M2Version::Dragonflight,
11 => M2Version::TheWarWithin,
_ => {
return Err(crate::error::M2Error::UnsupportedVersion(format!(
"Unknown WoW version: {major}"
)));
}
})
}
pub fn from_expansion_name(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"vanilla" | "classic" => Ok(M2Version::Vanilla),
"tbc" | "bc" | "burningcrusade" | "burning_crusade" => Ok(M2Version::TBC),
"wotlk" | "wrath" | "lichking" | "lich_king" | "wlk" => Ok(M2Version::WotLK),
"cata" | "cataclysm" => Ok(M2Version::Cataclysm),
"mop" | "pandaria" | "mists" | "mists_of_pandaria" => Ok(M2Version::MoP),
"wod" | "draenor" | "warlords" | "warlords_of_draenor" => Ok(M2Version::WoD),
"legion" => Ok(M2Version::Legion),
"bfa" | "bfazeroth" | "battle_for_azeroth" | "battleforazeroth" => Ok(M2Version::BfA),
"sl" | "shadowlands" => Ok(M2Version::Shadowlands),
"df" | "dragonflight" => Ok(M2Version::Dragonflight),
"tww" | "warwithin" | "the_war_within" | "thewarwithin" => Ok(M2Version::TheWarWithin),
_ => {
Self::from_string(s)
}
}
}
pub fn from_header_version(version: u32) -> Option<Self> {
match version {
256 => Some(Self::Vanilla), 260 => Some(Self::TBC), 264 => Some(Self::WotLK), 272 => Some(Self::Cataclysm),
257..=259 => Some(Self::Vanilla),
261..=263 => Some(Self::TBC),
265..=271 => Some(Self::WotLK),
273..=279 => Some(Self::Legion), 280..=289 => Some(Self::BfA), 290..=299 => Some(Self::Shadowlands), 300..=309 => Some(Self::Dragonflight), 310..=399 => Some(Self::TheWarWithin),
8 => Some(Self::MoP),
10 => Some(Self::WoD),
11 => Some(Self::Legion),
16 => Some(Self::BfA),
17 => Some(Self::Shadowlands),
18 => Some(Self::Dragonflight),
19 => Some(Self::TheWarWithin),
_ => None,
}
}
pub fn to_header_version(&self) -> u32 {
match self {
Self::Vanilla => 256, Self::TBC => 260, Self::WotLK => 264, Self::Cataclysm => 272, Self::MoP => 272,
Self::WoD => 275, Self::Legion => 276, Self::BfA => 280, Self::Shadowlands => 290, Self::Dragonflight => 300, Self::TheWarWithin => 310, }
}
pub fn expansion_name(&self) -> &'static str {
match self {
Self::Vanilla => "Vanilla",
Self::TBC => "The Burning Crusade",
Self::WotLK => "Wrath of the Lich King",
Self::Cataclysm => "Cataclysm",
Self::MoP => "Mists of Pandaria",
Self::WoD => "Warlords of Draenor",
Self::Legion => "Legion",
Self::BfA => "Battle for Azeroth",
Self::Shadowlands => "Shadowlands",
Self::Dragonflight => "Dragonflight",
Self::TheWarWithin => "The War Within",
}
}
pub fn to_version_string(&self) -> &'static str {
match self {
Self::Vanilla => "1.12.1",
Self::TBC => "2.4.3",
Self::WotLK => "3.3.5a",
Self::Cataclysm => "4.3.4",
Self::MoP => "5.4.8",
Self::WoD => "6.2.4",
Self::Legion => "7.3.5",
Self::BfA => "8.3.7",
Self::Shadowlands => "9.2.7",
Self::Dragonflight => "10.2.0",
Self::TheWarWithin => "11.0.0",
}
}
pub fn has_direct_conversion_path(&self, target: &Self) -> bool {
let self_ord = *self as usize;
let target_ord = *target as usize;
(self_ord as isize - target_ord as isize).abs() == 1
}
pub fn supports_chunked_format(&self) -> bool {
match self {
Self::Vanilla | Self::TBC => false,
Self::WotLK | Self::Cataclysm | Self::MoP => true, Self::WoD
| Self::Legion
| Self::BfA
| Self::Shadowlands
| Self::Dragonflight
| Self::TheWarWithin => true,
}
}
pub fn uses_external_chunks(&self) -> bool {
match self {
Self::Vanilla | Self::TBC | Self::WotLK | Self::Cataclysm | Self::MoP | Self::WoD => {
false
}
Self::Legion
| Self::BfA
| Self::Shadowlands
| Self::Dragonflight
| Self::TheWarWithin => true,
}
}
pub fn uses_inline_data(&self) -> bool {
match self {
Self::Vanilla | Self::TBC | Self::WotLK | Self::Cataclysm | Self::MoP | Self::WoD => {
true
}
Self::Legion
| Self::BfA
| Self::Shadowlands
| Self::Dragonflight
| Self::TheWarWithin => false,
}
}
pub fn uses_new_skin_format(&self) -> bool {
match self {
Self::Vanilla | Self::TBC | Self::WotLK => false,
Self::Cataclysm
| Self::MoP
| Self::WoD
| Self::Legion
| Self::BfA
| Self::Shadowlands
| Self::Dragonflight
| Self::TheWarWithin => true,
}
}
pub fn empirical_version_number(&self) -> Option<u32> {
match self {
Self::Vanilla => Some(256),
Self::TBC => Some(260),
Self::WotLK => Some(264),
Self::Cataclysm => Some(272),
Self::MoP => Some(272),
_ => None, }
}
pub fn requires_chunked_format(&self) -> bool {
matches!(
self,
Self::Legion | Self::BfA | Self::Shadowlands | Self::Dragonflight | Self::TheWarWithin
)
}
pub fn detect_expansion(version: u32) -> M2Version {
match version {
256..=259 => M2Version::Vanilla,
260..=263 => M2Version::TBC,
264..=271 => M2Version::WotLK,
272 => M2Version::Cataclysm, 273..=279 => M2Version::Legion,
280..=289 => M2Version::BfA,
290..=299 => M2Version::Shadowlands,
300..=309 => M2Version::Dragonflight,
310.. => M2Version::TheWarWithin,
_ => M2Version::Vanilla, }
}
}
impl std::fmt::Display for M2Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} ({})",
self.expansion_name(),
self.to_version_string()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_from_string() {
assert_eq!(
M2Version::from_string("1.12.1").unwrap(),
M2Version::Vanilla
);
assert_eq!(M2Version::from_string("2.4.3").unwrap(), M2Version::TBC);
assert_eq!(M2Version::from_string("3.3.5a").unwrap(), M2Version::WotLK);
assert_eq!(
M2Version::from_string("4.3.4").unwrap(),
M2Version::Cataclysm
);
assert_eq!(M2Version::from_string("5.4.8").unwrap(), M2Version::MoP);
}
#[test]
fn test_version_from_expansion_name() {
assert_eq!(
M2Version::from_expansion_name("classic").unwrap(),
M2Version::Vanilla
);
assert_eq!(
M2Version::from_expansion_name("TBC").unwrap(),
M2Version::TBC
);
assert_eq!(
M2Version::from_expansion_name("wotlk").unwrap(),
M2Version::WotLK
);
assert_eq!(
M2Version::from_expansion_name("cata").unwrap(),
M2Version::Cataclysm
);
assert_eq!(
M2Version::from_expansion_name("MoP").unwrap(),
M2Version::MoP
);
assert_eq!(
M2Version::from_expansion_name("3.3.5a").unwrap(),
M2Version::WotLK
);
}
#[test]
fn test_header_version_conversion() {
assert_eq!(
M2Version::from_header_version(256),
Some(M2Version::Vanilla)
);
assert_eq!(M2Version::from_header_version(260), Some(M2Version::TBC));
assert_eq!(M2Version::from_header_version(264), Some(M2Version::WotLK));
assert_eq!(
M2Version::from_header_version(272),
Some(M2Version::Cataclysm)
);
assert_eq!(
M2Version::from_header_version(257),
Some(M2Version::Vanilla)
);
assert_eq!(M2Version::from_header_version(261), Some(M2Version::TBC));
assert_eq!(M2Version::from_header_version(265), Some(M2Version::WotLK));
assert_eq!(M2Version::from_header_version(273), Some(M2Version::Legion));
assert_eq!(M2Version::from_header_version(276), Some(M2Version::Legion));
assert_eq!(M2Version::from_header_version(280), Some(M2Version::BfA));
assert_eq!(
M2Version::from_header_version(290),
Some(M2Version::Shadowlands)
);
assert_eq!(
M2Version::from_header_version(300),
Some(M2Version::Dragonflight)
);
assert_eq!(
M2Version::from_header_version(310),
Some(M2Version::TheWarWithin)
);
assert_eq!(M2Version::from_header_version(8), Some(M2Version::MoP));
assert_eq!(M2Version::from_header_version(10), Some(M2Version::WoD));
assert_eq!(M2Version::from_header_version(11), Some(M2Version::Legion));
assert_eq!(M2Version::from_header_version(16), Some(M2Version::BfA));
assert_eq!(
M2Version::from_header_version(17),
Some(M2Version::Shadowlands)
);
assert_eq!(
M2Version::from_header_version(18),
Some(M2Version::Dragonflight)
);
assert_eq!(
M2Version::from_header_version(19),
Some(M2Version::TheWarWithin)
);
assert_eq!(M2Version::from_header_version(1), None);
assert_eq!(M2Version::from_header_version(5), None);
assert_eq!(M2Version::from_header_version(999), None); }
#[test]
fn test_conversion_paths() {
assert!(M2Version::Vanilla.has_direct_conversion_path(&M2Version::TBC));
assert!(M2Version::TBC.has_direct_conversion_path(&M2Version::WotLK));
assert!(M2Version::WotLK.has_direct_conversion_path(&M2Version::Cataclysm));
assert!(!M2Version::Vanilla.has_direct_conversion_path(&M2Version::MoP));
assert!(!M2Version::Vanilla.has_direct_conversion_path(&M2Version::TheWarWithin));
}
#[test]
fn test_empirical_version_features() {
assert!(!M2Version::Vanilla.supports_chunked_format());
assert!(!M2Version::TBC.supports_chunked_format());
assert!(M2Version::WotLK.supports_chunked_format());
assert!(M2Version::Cataclysm.supports_chunked_format());
assert!(M2Version::MoP.supports_chunked_format());
assert!(!M2Version::Vanilla.uses_external_chunks());
assert!(!M2Version::TBC.uses_external_chunks());
assert!(!M2Version::WotLK.uses_external_chunks());
assert!(!M2Version::Cataclysm.uses_external_chunks());
assert!(!M2Version::MoP.uses_external_chunks());
assert!(!M2Version::WoD.uses_external_chunks());
assert!(M2Version::Legion.uses_external_chunks());
assert!(M2Version::BfA.uses_external_chunks());
assert!(M2Version::Vanilla.uses_inline_data());
assert!(M2Version::TBC.uses_inline_data());
assert!(M2Version::WotLK.uses_inline_data());
assert!(M2Version::Cataclysm.uses_inline_data());
assert!(M2Version::MoP.uses_inline_data());
assert!(M2Version::WoD.uses_inline_data());
assert!(!M2Version::Legion.uses_inline_data());
assert!(!M2Version::BfA.uses_inline_data());
}
#[test]
fn test_empirical_version_numbers() {
assert_eq!(M2Version::Vanilla.empirical_version_number(), Some(256));
assert_eq!(M2Version::TBC.empirical_version_number(), Some(260));
assert_eq!(M2Version::WotLK.empirical_version_number(), Some(264));
assert_eq!(M2Version::Cataclysm.empirical_version_number(), Some(272));
assert_eq!(M2Version::MoP.empirical_version_number(), Some(272));
assert_eq!(M2Version::WoD.empirical_version_number(), None);
assert_eq!(M2Version::Legion.empirical_version_number(), None);
}
#[test]
fn test_header_version_roundtrip() {
assert_eq!(M2Version::Vanilla.to_header_version(), 256);
assert_eq!(M2Version::TBC.to_header_version(), 260);
assert_eq!(M2Version::WotLK.to_header_version(), 264);
assert_eq!(M2Version::Cataclysm.to_header_version(), 272);
assert_eq!(M2Version::MoP.to_header_version(), 272);
assert_eq!(M2Version::Legion.to_header_version(), 276);
assert_eq!(M2Version::BfA.to_header_version(), 280);
assert_eq!(M2Version::Shadowlands.to_header_version(), 290);
assert_eq!(
M2Version::from_header_version(M2Version::Vanilla.to_header_version()),
Some(M2Version::Vanilla)
);
assert_eq!(
M2Version::from_header_version(M2Version::TBC.to_header_version()),
Some(M2Version::TBC)
);
assert_eq!(
M2Version::from_header_version(M2Version::WotLK.to_header_version()),
Some(M2Version::WotLK)
);
assert_eq!(
M2Version::from_header_version(M2Version::Cataclysm.to_header_version()),
Some(M2Version::Cataclysm)
);
assert_eq!(
M2Version::from_header_version(M2Version::Legion.to_header_version()),
Some(M2Version::Legion)
);
}
#[test]
fn test_chunked_format_detection() {
assert!(!M2Version::Vanilla.requires_chunked_format());
assert!(!M2Version::TBC.requires_chunked_format());
assert!(!M2Version::WotLK.requires_chunked_format());
assert!(!M2Version::Cataclysm.requires_chunked_format());
assert!(!M2Version::MoP.requires_chunked_format());
assert!(!M2Version::WoD.requires_chunked_format());
assert!(M2Version::Legion.requires_chunked_format());
assert!(M2Version::BfA.requires_chunked_format());
}
#[test]
fn test_expansion_detection() {
assert_eq!(M2Version::detect_expansion(256), M2Version::Vanilla);
assert_eq!(M2Version::detect_expansion(260), M2Version::TBC);
assert_eq!(M2Version::detect_expansion(264), M2Version::WotLK);
assert_eq!(M2Version::detect_expansion(272), M2Version::Cataclysm);
assert_eq!(M2Version::detect_expansion(273), M2Version::Legion);
assert_eq!(M2Version::detect_expansion(276), M2Version::Legion);
assert_eq!(M2Version::detect_expansion(280), M2Version::BfA);
assert_eq!(M2Version::detect_expansion(310), M2Version::TheWarWithin);
}
}