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;