use std::cmp::Ordering;
use std::fmt::Display;
use std::fmt::Formatter;
use crate::prelude::*;
use crate::wad::deserialize::reader::DataReader;
use crate::wad::elements::GMElement;
use crate::wad::serialize::builder::DataBuilder;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LTSBranch {
PreLTS,
LTS,
PostLTS,
}
impl Display for LTSBranch {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let string = match self {
Self::PreLTS => "PreLTS",
Self::LTS => "LTS",
Self::PostLTS => "PostLTS",
};
f.write_str(string)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub struct GMVersion {
pub major: u32,
pub minor: u32,
pub release: u32,
pub build: u32,
pub branch: LTSBranch,
}
impl GMVersion {
pub const GMS2: Self = Self::new(2, 0, 0, 0, LTSBranch::PreLTS);
#[must_use]
pub const fn new(major: u32, minor: u32, release: u32, build: u32, branch: LTSBranch) -> Self {
Self { major, minor, release, build, branch }
}
}
impl Default for GMVersion {
fn default() -> Self {
Self::GMS2
}
}
impl Display for GMVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write_version(f, self.major, self.minor, self.release, self.build)?;
if self.major >= 2022 {
write!(f, " ({})", self.branch)?;
}
Ok(())
}
}
impl GMVersion {
#[must_use]
pub fn is_version_at_least(&self, version_req: impl Into<GMVersionReq>) -> bool {
self >= &version_req.into()
}
pub fn set_version(&mut self, req: impl Into<GMVersionReq>) {
let new = req.into();
self.major = new.major;
self.minor = new.minor;
self.release = new.release;
self.build = new.build;
if new.post_lts {
self.branch = LTSBranch::PostLTS;
} else if new.major >= 2022 {
self.branch = LTSBranch::LTS;
}
}
pub fn set_version_at_least(&mut self, req: impl Into<GMVersionReq>) -> Result<()> {
let new_ver: GMVersionReq = req.into();
if !matches!(new_ver.major, 2 | 2022..=2026) {
let comment = if new_ver.major > 2026 && new_ver.major < 2100 {
format!(
"! If the current year is {} or greater, please contact the maintainer of \
this project to update the version validation.",
new_ver.major
)
} else {
String::new()
};
bail!("Upgrading GameMaker Version from {self} to {new_ver} is not allowed{comment}");
}
if *self < new_ver {
self.set_version(new_ver);
}
Ok(())
}
}
impl PartialEq<GMVersionReq> for GMVersion {
fn eq(&self, req: &GMVersionReq) -> bool {
if req.post_lts {
return self.branch == LTSBranch::PostLTS;
}
self.major == req.major
&& self.minor == req.minor
&& self.release == req.release
&& self.build == req.build
}
}
impl PartialOrd<GMVersionReq> for GMVersion {
fn partial_cmp(&self, req: &GMVersionReq) -> Option<Ordering> {
if req.post_lts && self.branch < LTSBranch::PostLTS {
return Some(Ordering::Less);
}
macro_rules! cmp {
($part:ident) => {
match self.$part.cmp(&req.$part) {
Ordering::Equal => {}
other => return Some(other),
}
};
}
cmp!(major);
cmp!(minor);
cmp!(release);
cmp!(build);
Some(Ordering::Equal)
}
}
impl GMElement for GMVersion {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let major = reader.read_u32()?;
let minor = reader.read_u32()?;
let release = reader.read_u32()?;
let build = reader.read_u32()?;
Ok(Self::new(major, minor, release, build, LTSBranch::PreLTS))
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
builder.write_u32(self.major);
builder.write_u32(self.minor);
builder.write_u32(self.release);
builder.write_u32(self.build);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GMVersionReq {
pub major: u32,
pub minor: u32,
pub release: u32,
pub build: u32,
pub post_lts: bool,
}
impl GMVersionReq {
pub const NONE: Self = Self::new(0, 0, 0, 0, LTSBranch::PreLTS);
#[must_use]
pub const fn new(major: u32, minor: u32, release: u32, build: u32, lts: LTSBranch) -> Self {
let post_lts: bool = matches!(lts, LTSBranch::PostLTS);
Self { major, minor, release, build, post_lts }
}
}
impl From<(u32, u32)> for GMVersionReq {
fn from((major, minor): (u32, u32)) -> Self {
Self {
major,
minor,
release: 0,
build: 0,
post_lts: false,
}
}
}
impl From<(u32, u32, u32)> for GMVersionReq {
fn from((major, minor, release): (u32, u32, u32)) -> Self {
Self {
major,
minor,
release,
build: 0,
post_lts: false,
}
}
}
impl From<(u32, u32, u32, u32)> for GMVersionReq {
fn from((major, minor, release, build): (u32, u32, u32, u32)) -> Self {
Self {
major,
minor,
release,
build,
post_lts: false,
}
}
}
impl From<(u32, u32, LTSBranch)> for GMVersionReq {
fn from((major, minor, lts): (u32, u32, LTSBranch)) -> Self {
Self {
major,
minor,
release: 0,
build: 0,
post_lts: matches!(lts, LTSBranch::PostLTS),
}
}
}
impl From<(u32, u32, u32, LTSBranch)> for GMVersionReq {
fn from((major, minor, release, lts): (u32, u32, u32, LTSBranch)) -> Self {
Self {
major,
minor,
release,
build: 0,
post_lts: matches!(lts, LTSBranch::PostLTS),
}
}
}
impl From<(u32, u32, u32, u32, LTSBranch)> for GMVersionReq {
fn from((major, minor, release, build, lts): (u32, u32, u32, u32, LTSBranch)) -> Self {
Self {
major,
minor,
release,
build,
post_lts: matches!(lts, LTSBranch::PostLTS),
}
}
}
impl Display for GMVersionReq {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write_version(f, self.major, self.minor, self.release, self.build)?;
if self.post_lts {
write!(f, " (Post LTS)")?;
}
Ok(())
}
}
fn write_version(
f: &mut Formatter,
major: u32,
minor: u32,
release: u32,
build: u32,
) -> std::fmt::Result {
write!(f, "{major}")?;
match (minor, release, build) {
(minor, 0, 0) => write!(f, ".{minor}"),
(minor, release, 0) => write!(f, ".{minor}.{release}"),
(minor, release, build) => {
write!(f, ".{minor}.{release}.{build}")
}
}
}