diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj
index b2647f11..5f68c8f7 100644
--- a/IPA.Loader/IPA.Loader.csproj
+++ b/IPA.Loader/IPA.Loader.csproj
@@ -62,6 +62,7 @@
+
@@ -93,6 +94,7 @@
+
diff --git a/IPA.Loader/JsonConverters/AlmostVersionConverter.cs b/IPA.Loader/JsonConverters/AlmostVersionConverter.cs
new file mode 100644
index 00000000..f2820705
--- /dev/null
+++ b/IPA.Loader/JsonConverters/AlmostVersionConverter.cs
@@ -0,0 +1,22 @@
+using IPA.Utilities;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace IPA.JsonConverters
+{
+ internal class AlmostVersionConverter : JsonConverter
+ {
+ public override AlmostVersion ReadJson(JsonReader reader, Type objectType, AlmostVersion existingValue, bool hasExistingValue, JsonSerializer serializer) =>
+ reader.Value == null ? null : new AlmostVersion(reader.Value as string);
+
+ public override void WriteJson(JsonWriter writer, AlmostVersion value, JsonSerializer serializer)
+ {
+ if (value == null) writer.WriteNull();
+ else writer.WriteValue(value.ToString());
+ }
+ }
+}
diff --git a/IPA.Loader/Loader/PluginManager.cs b/IPA.Loader/Loader/PluginManager.cs
index cad6dd88..d30f1c1d 100644
--- a/IPA.Loader/Loader/PluginManager.cs
+++ b/IPA.Loader/Loader/PluginManager.cs
@@ -294,9 +294,9 @@ namespace IPA.Loader
string pluginDir = BeatSaber.PluginsPath;
var gameVer = BeatSaber.GameVersion;
var lastVerS = SelfConfig.SelfConfigRef.Value.LastGameVersion;
- var lastVer = lastVerS != null ? new SemVer.Version(lastVerS, true) : null;
+ var lastVer = lastVerS != null ? new AlmostVersion(lastVerS, gameVer) : null;
- if (lastVer != null && Utils.VersionCompareNoPrerelease(gameVer, lastVer) != 0)
+ if (lastVer != null && gameVer != lastVer)
{
var oldPluginsName = Path.Combine(BeatSaber.InstallPath, $"Old {lastVer} Plugins");
var newPluginsName = Path.Combine(BeatSaber.InstallPath, $"Old {gameVer} Plugins");
diff --git a/IPA.Loader/Loader/PluginManifest.cs b/IPA.Loader/Loader/PluginManifest.cs
index 0a3dcf47..ae621ea4 100644
--- a/IPA.Loader/Loader/PluginManifest.cs
+++ b/IPA.Loader/Loader/PluginManifest.cs
@@ -1,4 +1,5 @@
using IPA.JsonConverters;
+using IPA.Utilities;
using Newtonsoft.Json;
using SemVer;
using System;
@@ -21,8 +22,8 @@ namespace IPA.Loader
[JsonProperty("version", Required = Required.Always), JsonConverter(typeof(SemverVersionConverter))]
public Version Version;
- [JsonProperty("gameVersion", Required = Required.Always), JsonConverter(typeof(SemverVersionConverter))]
- public Version GameVersion;
+ [JsonProperty("gameVersion", Required = Required.Always), JsonConverter(typeof(AlmostVersionConverter))]
+ public AlmostVersion GameVersion;
[JsonProperty("author", Required = Required.Always)]
public string Author;
diff --git a/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs b/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs
index b34fe5ab..551583fe 100644
--- a/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs
+++ b/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs
@@ -103,8 +103,8 @@ namespace IPA.Updating.BeatMods
public Version Version;
[JsonProperty("gameVersion"),
- JsonConverter(typeof(SemverVersionConverter))]
- public Version GameVersion;
+ JsonConverter(typeof(AlmostVersionConverter))]
+ public AlmostVersion GameVersion;
[Serializable]
public class AuthorType
diff --git a/IPA.Loader/Updating/BeatMods/Updater.cs b/IPA.Loader/Updating/BeatMods/Updater.cs
index d3bbad82..932be6ca 100644
--- a/IPA.Loader/Updating/BeatMods/Updater.cs
+++ b/IPA.Loader/Updating/BeatMods/Updater.cs
@@ -389,7 +389,7 @@ namespace IPA.Updating.BeatMods
var ver = modsMatching.Value
.Where(nullCheck => nullCheck != null) // entry is not null
- .Where(versionCheck => Utils.VersionCompareNoPrerelease(versionCheck.GameVersion, BeatSaber.GameVersion) == 0) // game version matches
+ .Where(versionCheck => versionCheck.GameVersion == BeatSaber.GameVersion) // game version matches
.Where(approvalCheck => approvalCheck.Status == ApiEndpoint.Mod.ApprovedStatus) // version approved
// TODO: fix; it seems wrong somehow
.Where(conflictsCheck => dep.Conflicts == null || !dep.Conflicts.IsSatisfied(conflictsCheck.Version)) // not a conflicting version
diff --git a/IPA.Loader/Utilities/AlmostVersion.cs b/IPA.Loader/Utilities/AlmostVersion.cs
new file mode 100644
index 00000000..46b12cdf
--- /dev/null
+++ b/IPA.Loader/Utilities/AlmostVersion.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SemVer;
+using Version = SemVer.Version;
+
+namespace IPA.Utilities
+{
+ public class AlmostVersion : IComparable, IComparable
+ {
+ private Version semverForm = null;
+ private string strForm = null;
+ private StoredAs storedAs;
+
+ public enum StoredAs
+ {
+ SemVer,
+ String
+ }
+
+ public AlmostVersion(string vertext)
+ {
+ if (!TryParseFrom(vertext, StoredAs.SemVer))
+ TryParseFrom(vertext, StoredAs.String);
+ }
+
+ public AlmostVersion(Version ver)
+ {
+ semverForm = ver;
+ storedAs = StoredAs.SemVer;
+ }
+
+ public AlmostVersion(string vertext, StoredAs mode)
+ {
+ if (!TryParseFrom(vertext, mode))
+ throw new ArgumentException($"{nameof(vertext)} could not be stored as {mode}!");
+ }
+
+ public AlmostVersion(string vertext, AlmostVersion copyMode)
+ {
+ if (copyMode == null)
+ throw new ArgumentNullException(nameof(copyMode));
+
+ if (!TryParseFrom(vertext, copyMode.storedAs))
+ throw new ArgumentException($"{nameof(vertext)} could not be stored the same way as {copyMode}!");
+ }
+
+ private bool TryParseFrom(string str, StoredAs mode)
+ {
+ if (mode == StoredAs.SemVer)
+ try
+ {
+ semverForm = new Version(str, true);
+ storedAs = StoredAs.SemVer;
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ else
+ {
+ strForm = str;
+ storedAs = StoredAs.String;
+ return true;
+ }
+ }
+
+ public string StringValue => strForm;
+
+ public Version SemverValue => semverForm;
+
+ public override string ToString() =>
+ storedAs == StoredAs.SemVer ? semverForm.ToString() : strForm;
+
+ public int CompareTo(AlmostVersion other)
+ {
+ if (other == null) return -1;
+ if (storedAs != other.storedAs)
+ throw new InvalidOperationException("Cannot compare AlmostVersions with different stores!");
+
+ if (storedAs == StoredAs.SemVer)
+ return semverForm.CompareTo(other.semverForm);
+ else
+ return strForm.CompareTo(other.strForm);
+ }
+
+ public int CompareTo(Version other)
+ {
+ if (storedAs != StoredAs.SemVer)
+ throw new InvalidOperationException("Cannot compare a SemVer version with an AlmostVersion stored as a string!");
+
+ return semverForm.CompareTo(other);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is AlmostVersion version &&
+ semverForm == version.semverForm &&
+ strForm == version.strForm &&
+ storedAs == version.storedAs;
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = -126402897;
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(semverForm);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(strForm);
+ hashCode = hashCode * -1521134295 + storedAs.GetHashCode();
+ return hashCode;
+ }
+
+ public static bool operator==(AlmostVersion l, AlmostVersion r)
+ {
+ if (l.storedAs != r.storedAs) return false;
+ if (l.storedAs == StoredAs.SemVer)
+ return Utils.VersionCompareNoPrerelease(l.semverForm, r.semverForm) == 0;
+ else
+ return l.strForm == r.strForm;
+ }
+
+ public static bool operator!=(AlmostVersion l, AlmostVersion r) => !(l == r);
+
+ // implicitly convertible from Version
+ public static implicit operator AlmostVersion(Version ver) => new AlmostVersion(ver);
+
+ // implicitly convertible to Version
+ public static implicit operator Version(AlmostVersion av) => av.SemverValue;
+ }
+}
diff --git a/IPA.Loader/Utilities/BeatSaber.cs b/IPA.Loader/Utilities/BeatSaber.cs
index 3629d4f5..2f9e42c5 100644
--- a/IPA.Loader/Utilities/BeatSaber.cs
+++ b/IPA.Loader/Utilities/BeatSaber.cs
@@ -11,22 +11,22 @@ namespace IPA.Utilities
///
public static class BeatSaber
{
- private static Version _gameVersion;
+ private static AlmostVersion _gameVersion;
///
/// Provides the current game version.
///
/// the SemVer version of the game
- public static Version GameVersion => _gameVersion ?? (_gameVersion = new Version(Application.version, true));
+ public static AlmostVersion GameVersion => _gameVersion ?? (_gameVersion = new AlmostVersion(Application.version));
- internal static void SetEarlyGameVersion(Version ver)
+ internal static void SetEarlyGameVersion(AlmostVersion ver)
{
_gameVersion = ver;
Logging.Logger.log.Debug($"GameVersion set early to {ver}");
}
internal static void EnsureRuntimeGameVersion()
{
- var rtVer = new Version(Application.version, true);
- if (rtVer != _gameVersion)
+ var rtVer = new AlmostVersion(Application.version);
+ if (!rtVer.Equals(_gameVersion)) // this actually uses stricter equality than == for AlmostVersion
{
Logging.Logger.log.Warn($"Early version {_gameVersion} parsed from game files doesn't match runtime version {rtVer}!");
_gameVersion = rtVer;