diff --git a/IPA.Injector/Updates.cs b/IPA.Injector/Updates.cs index aea8891c..663095c5 100644 --- a/IPA.Injector/Updates.cs +++ b/IPA.Injector/Updates.cs @@ -9,7 +9,7 @@ namespace IPA.Injector { internal static class Updates { - private const string DeleteFileName = Updating.ModSaber.Updater.SpecialDeletionsFile; + private const string DeleteFileName = Updating.BeatMods.Updater.SpecialDeletionsFile; public static void InstallPendingUpdates() { var pendingDir = Path.Combine(BeatSaber.InstallPath, "IPA", "Pending"); diff --git a/IPA.Loader/Config/SelfConfig.cs b/IPA.Loader/Config/SelfConfig.cs index b2e4328a..f15c5ec2 100644 --- a/IPA.Loader/Config/SelfConfig.cs +++ b/IPA.Loader/Config/SelfConfig.cs @@ -33,7 +33,7 @@ namespace IPA.Config } internal const string IPAName = "Beat Saber IPA"; - internal const string IPAVersion = "3.12.9"; + internal const string IPAVersion = "3.12.10"; public bool Regenerate = true; diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 73f38bb1..2613576f 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -89,7 +89,6 @@ - @@ -105,8 +104,8 @@ - - + + diff --git a/IPA.Loader/JsonConverters/ModSaberDependencyConverter.cs b/IPA.Loader/JsonConverters/ModSaberDependencyConverter.cs deleted file mode 100644 index 18cfa2e0..00000000 --- a/IPA.Loader/JsonConverters/ModSaberDependencyConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using IPA.Updating.ModSaber; -using Newtonsoft.Json; -using SemVer; - -namespace IPA.JsonConverters -{ - internal class ModSaberDependencyConverter : JsonConverter - { - public override ApiEndpoint.Mod.Dependency ReadJson(JsonReader reader, Type objectType, ApiEndpoint.Mod.Dependency existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var parts = (reader.Value as string)?.Split('@'); - return new ApiEndpoint.Mod.Dependency - { - Name = parts?[0], - VersionRange = new Range(parts?[1]) - }; - } - - public override void WriteJson(JsonWriter writer, ApiEndpoint.Mod.Dependency value, JsonSerializer serializer) - { - writer.WriteValue($"{value.Name}@{value.VersionRange}"); - } - } -} diff --git a/IPA.Loader/Loader/PluginComponent.cs b/IPA.Loader/Loader/PluginComponent.cs index 5ab3c2cb..b2b4553c 100644 --- a/IPA.Loader/Loader/PluginComponent.cs +++ b/IPA.Loader/Loader/PluginComponent.cs @@ -28,7 +28,7 @@ namespace IPA.Loader #pragma warning restore 618 /* kill this for now, until theres a new system */ - //gameObject.AddComponent(); + gameObject.AddComponent(); bsPlugins.OnApplicationStart(); ipaPlugins.OnApplicationStart(); diff --git a/IPA.Loader/Loader/manifest.json b/IPA.Loader/Loader/manifest.json index f2e855e3..985a963e 100644 --- a/IPA.Loader/Loader/manifest.json +++ b/IPA.Loader/Loader/manifest.json @@ -3,9 +3,9 @@ "author": "DaNike", "description": "A mod loader specifically for Beat Saber.", "gameVersion": "0.13.2", - "id": "beatsaber-ipa-reloaded", - "name": "BSIPA", - "version": "3.12.9", + "id": "BSIPA", + "name": "Beat Saber IPA", + "version": "3.12.10", "features": [ "define-feature(print, IPA.Loader.Features.PrintFeature)", "define-feature(debug, IPA.Loader.Features.DebugFeature)", diff --git a/IPA.Loader/Updating/ModSaber/ApiEndpoint.cs b/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs similarity index 65% rename from IPA.Loader/Updating/ModSaber/ApiEndpoint.cs rename to IPA.Loader/Updating/BeatMods/ApiEndpoint.cs index 72d51825..d61c2f77 100644 --- a/IPA.Loader/Updating/ModSaber/ApiEndpoint.cs +++ b/IPA.Loader/Updating/BeatMods/ApiEndpoint.cs @@ -7,13 +7,14 @@ using Newtonsoft.Json; using SemVer; using Version = SemVer.Version; -namespace IPA.Updating.ModSaber +namespace IPA.Updating.BeatMods { class ApiEndpoint { - public const string ApiBase = "https://www.modsaber.org/"; - public const string GetModInfoEndpoint = "registry/{0}/{1}"; - public const string GetModsWithSemver = "api/v1.1/mods/semver/{0}/{1}"; + public const string BeatModBase = "https://beatmods.com"; + public const string ApiBase = BeatModBase + "/api/v1/mod"; + public const string GetModInfoEndpoint = "?name={0}&version={1}"; + public const string GetModsByName = "?name={0}"; class HexArrayConverter : JsonConverter { @@ -64,6 +65,15 @@ namespace IPA.Updating.ModSaber public class Mod { #pragma warning disable CS0649 + /// + /// Will be a useless string of characters. Do not use. + /// + [JsonProperty("_id")] + public string Id; + + [JsonProperty("required")] + public bool Required; + [JsonProperty("name")] public string Name; @@ -74,15 +84,18 @@ namespace IPA.Updating.ModSaber [Serializable] public class AuthorType { - [JsonProperty("name")] + [JsonProperty("username")] public string Name; - [JsonProperty("id")] + [JsonProperty("_id")] public string Id; public override string ToString() => Name; } - [Serializable] + [JsonProperty("author")] + public AuthorType Author; + + /*[Serializable] public class DetailsData { [JsonProperty("author")] @@ -96,35 +109,23 @@ namespace IPA.Updating.ModSaber } [JsonProperty("details")] - public DetailsData Details; + public DetailsData Details;*/ - [Serializable] - public class ApprovalStatus - { - [JsonProperty("status")] - public bool Status; - [JsonProperty("modified")] - public string LastModified; - } + [JsonProperty("status")] + public string Status; + public const string ApprovedStatus = "approved"; - [JsonProperty("approval")] - public ApprovalStatus Approval; - - [Serializable] - public class GameVersionType - { - [JsonProperty("value"), - JsonConverter(typeof(SemverVersionConverter))] - public Version Version; - [JsonProperty("manifest")] - public string Manifest; - } + [JsonProperty("description")] + public string Description; - [JsonProperty("gameVersion")] - public GameVersionType GameVersion; + [JsonProperty("category")] + public string Category; + [JsonProperty("link")] + public Uri Link; + #pragma warning restore CS0649 - [Serializable] + /*[Serializable] public class PlatformFile { [JsonProperty("hash"), @@ -152,9 +153,42 @@ namespace IPA.Updating.ModSaber } [JsonProperty("files")] - public FilesObject Files; - - public class Dependency + public FilesObject Files;*/ + + [Serializable] + public class DownloadsObject + { + public const string TypeUniversal = "universal"; + public const string TypeSteam = "steam"; + public const string TypeOculus = "oculus"; + + [JsonProperty("type")] + public string Type; + + [JsonProperty("url")] + public string Path; + + [Serializable] + public class HashObject + { + [JsonProperty("hash"), JsonConverter(typeof(HexArrayConverter))] + public byte[] Hash; + + [JsonProperty("file")] + public string File; + } + + /// + /// Hashes stored are MD5 + /// + [JsonProperty("hashMd5")] + public HashObject[] Hashes; + } + + [JsonProperty("downloads")] + public DownloadsObject[] Downloads; + + /*public class Dependency { public string Name = null; public Range VersionRange = null; @@ -174,11 +208,14 @@ namespace IPA.Updating.ModSaber public LinksType Links; [JsonProperty("oldVersions", ItemConverterType = typeof(SemverVersionConverter))] - public Version[] OldVersions = new Version[0]; + public Version[] OldVersions = new Version[0];*/ + + [JsonProperty("dependencies")] + public Mod[] Dependencies; public override string ToString() { - return $"{{\"{Details.Title} ({Name})\"v{Version} for {GameVersion.Version} by {Details.Author} with \"{Files.Steam}\" and \"{Files.Oculus}\"}}"; + return $"{{\"{Name}\"v{Version} by {Author} files for {string.Join(", ", Downloads.Select(d => d.Type))}}}"; } } diff --git a/IPA.Loader/Updating/ModSaber/Updater.cs b/IPA.Loader/Updating/BeatMods/Updater.cs similarity index 88% rename from IPA.Loader/Updating/ModSaber/Updater.cs rename to IPA.Loader/Updating/BeatMods/Updater.cs index 64536ff4..2768da53 100644 --- a/IPA.Loader/Updating/ModSaber/Updater.cs +++ b/IPA.Loader/Updating/BeatMods/Updater.cs @@ -20,7 +20,7 @@ using static IPA.Loader.PluginManager; using Logger = IPA.Logging.Logger; using Version = SemVer.Version; -namespace IPA.Updating.ModSaber +namespace IPA.Updating.BeatMods { [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] internal class Updater : MonoBehaviour @@ -72,7 +72,7 @@ namespace IPA.Updating.ModSaber } private readonly Dictionary requestCache = new Dictionary(); - private IEnumerator GetModsaberEndpoint(string url, Ref result) + private IEnumerator GetBeatModsEndpoint(string url, Ref result) { if (requestCache.TryGetValue(url, out string value)) { @@ -121,11 +121,11 @@ namespace IPA.Updating.ModSaber { Ref reqResult = new Ref(""); - yield return GetModsaberEndpoint(uri, reqResult); + yield return GetBeatModsEndpoint(uri, reqResult); try { - result.Value = JsonConvert.DeserializeObject(reqResult.Value); + result.Value = JsonConvert.DeserializeObject>(reqResult.Value).First(); modCache[uri] = result.Value; } @@ -137,9 +137,9 @@ namespace IPA.Updating.ModSaber } private readonly Dictionary> modVersionsCache = new Dictionary>(); - private IEnumerator GetModVersionsMatching(string modName, string range, Ref> result) + private IEnumerator GetModVersionsMatching(string modName, Range range, Ref> result) { - var uri = string.Format(ApiEndpoint.GetModsWithSemver, Uri.EscapeUriString(modName), Uri.EscapeUriString(range)); + var uri = string.Format(ApiEndpoint.GetModsByName, Uri.EscapeUriString(modName)); if (modVersionsCache.TryGetValue(uri, out List value)) { @@ -149,11 +149,12 @@ namespace IPA.Updating.ModSaber { Ref reqResult = new Ref(""); - yield return GetModsaberEndpoint(uri, reqResult); + yield return GetBeatModsEndpoint(uri, reqResult); try { - result.Value = JsonConvert.DeserializeObject>(reqResult.Value); + result.Value = JsonConvert.DeserializeObject>(reqResult.Value) + .Where(m => range.IsSatisfied(m.Version)).ToList(); modVersionsCache[uri] = result.Value; } @@ -217,8 +218,15 @@ namespace IPA.Updating.ModSaber continue; } - list.Value.AddRange(mod.Value.Links.Dependencies.Select(d => new DependencyObject { Name = d.Name, Requirement = d.VersionRange, Consumers = new HashSet { dep.Name } })); - list.Value.AddRange(mod.Value.Links.Conflicts.Select(d => new DependencyObject { Name = d.Name, Conflicts = d.VersionRange, Consumers = new HashSet { dep.Name } })); + list.Value.AddRange(mod.Value.Dependencies.Select(m => new DependencyObject + { + Name = m.Name, + Requirement = new Range($">={m.Version}"), + Consumers = new HashSet { dep.Name } + })); + // currently no conflicts exist in BeatMods + //list.Value.AddRange(mod.Value.Links.Dependencies.Select(d => new DependencyObject { Name = d.Name, Requirement = d.VersionRange, Consumers = new HashSet { dep.Name } })); + //list.Value.AddRange(mod.Value.Links.Conflicts.Select(d => new DependencyObject { Name = d.Name, Conflicts = d.VersionRange, Consumers = new HashSet { dep.Name } })); } var depNames = new HashSet(); @@ -266,7 +274,7 @@ namespace IPA.Updating.ModSaber } var modsMatching = new Ref>(null); - yield return GetModVersionsMatching(dep.Name, dep.Requirement.ToString(), modsMatching); + yield return GetModVersionsMatching(dep.Name, dep.Requirement, modsMatching); try { modsMatching.Verify(); } catch (Exception e) { @@ -278,8 +286,8 @@ namespace IPA.Updating.ModSaber var ver = modsMatching.Value .Where(nullCheck => nullCheck != null) // entry is not null - .Where(versionCheck => versionCheck.GameVersion.Version == BeatSaber.GameVersion) // game version matches - .Where(approvalCheck => approvalCheck.Approval.Status) // version approved + //.Where(versionCheck => versionCheck.GameVersion.Version == BeatSaber.GameVersion) // game version matches + .Where(approvalCheck => approvalCheck.Status == ApiEndpoint.Mod.ApprovedStatus) // version approved .Where(conflictsCheck => dep.Conflicts == null || !dep.Conflicts.IsSatisfied(conflictsCheck.Version)) // not a conflicting version .Select(mod => mod.Version).Max(); // (2.1) get the max version // ReSharper disable once AssignmentInConditionalExpression @@ -329,13 +337,18 @@ namespace IPA.Updating.ModSaber yield break; } - ApiEndpoint.Mod.PlatformFile platformFile; + /* + ApiEndpoint.Mod.DownloadsObject platformFile; if (BeatSaber.ReleaseType == BeatSaber.Release.Steam || mod.Value.Files.Oculus == null) platformFile = mod.Value.Files.Steam; else - platformFile = mod.Value.Files.Oculus; + platformFile = mod.Value.Files.Oculus;*/ + + var releaseName = BeatSaber.ReleaseType == BeatSaber.Release.Steam + ? ApiEndpoint.Mod.DownloadsObject.TypeSteam : ApiEndpoint.Mod.DownloadsObject.TypeOculus; + var platformFile = mod.Value.Downloads.First(f => f.Type == ApiEndpoint.Mod.DownloadsObject.TypeUniversal || f.Type == releaseName); - string url = platformFile.DownloadPath; + string url = ApiEndpoint.BeatModBase + platformFile.Path; Logger.updater.Debug($"URL = {url}"); @@ -386,7 +399,7 @@ namespace IPA.Updating.ModSaber if (downloadTask.IsFaulted) { - if (downloadTask.Exception != null && downloadTask.Exception.InnerExceptions.Any(e => e is ModsaberInterceptException)) + if (downloadTask.Exception != null && downloadTask.Exception.InnerExceptions.Any(e => e is BeatmodsInterceptException)) { // any exception is an intercept exception Logger.updater.Error($"Modsaber did not return expected data for {item.Name}"); } @@ -451,15 +464,15 @@ namespace IPA.Updating.ModSaber } } - private void ExtractPluginAsync(MemoryStream stream, DependencyObject item, ApiEndpoint.Mod.PlatformFile fileInfo) + private void ExtractPluginAsync(MemoryStream stream, DependencyObject item, ApiEndpoint.Mod.DownloadsObject fileInfo) { // (3.3) Logger.updater.Debug($"Extracting ZIP file for {item.Name}"); - var data = stream.GetBuffer(); + /*var data = stream.GetBuffer(); SHA1 sha = new SHA1CryptoServiceProvider(); var hash = sha.ComputeHash(data); if (!Utils.UnsafeCompare(hash, fileInfo.Hash)) - throw new Exception("The hash for the file doesn't match what is defined"); + throw new Exception("The hash for the file doesn't match what is defined");*/ var targetDir = Path.Combine(BeatSaber.InstallPath, "IPA", Path.GetRandomFileName() + "_Pending"); Directory.CreateDirectory(targetDir); @@ -489,17 +502,17 @@ namespace IPA.Updating.ModSaber entry.Extract(ostream); ostream.Seek(0, SeekOrigin.Begin); - sha = new SHA1CryptoServiceProvider(); - var fileHash = sha.ComputeHash(ostream); + var md5 = new MD5CryptoServiceProvider(); + var fileHash = md5.ComputeHash(ostream); try { - if (!Utils.UnsafeCompare(fileHash, fileInfo.FileHashes[entry.FileName])) + if (!Utils.UnsafeCompare(fileHash, fileInfo.Hashes.Where(h => h.File == entry.FileName).Select(h => h.Hash).First())) throw new Exception("The hash for the file doesn't match what is defined"); } catch (KeyNotFoundException) { - throw new ModsaberInterceptException("ModSaber did not send the hashes for the zip's content!"); + throw new BeatmodsInterceptException("BeatMods did not send the hashes for the zip's content!"); } ostream.Seek(0, SeekOrigin.Begin); @@ -582,21 +595,21 @@ namespace IPA.Updating.ModSaber } [Serializable] - internal class ModsaberInterceptException : Exception + internal class BeatmodsInterceptException : Exception { - public ModsaberInterceptException() + public BeatmodsInterceptException() { } - public ModsaberInterceptException(string message) : base(message) + public BeatmodsInterceptException(string message) : base(message) { } - public ModsaberInterceptException(string message, Exception innerException) : base(message, innerException) + public BeatmodsInterceptException(string message, Exception innerException) : base(message, innerException) { } - protected ModsaberInterceptException(SerializationInfo info, StreamingContext context) : base(info, context) + protected BeatmodsInterceptException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/IPA/Properties/AssemblyInfo.cs b/IPA/Properties/AssemblyInfo.cs index ac459016..9769ae7e 100644 --- a/IPA/Properties/AssemblyInfo.cs +++ b/IPA/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.12.9")] -[assembly: AssemblyFileVersion("3.12.9")] \ No newline at end of file +[assembly: AssemblyVersion("3.12.10")] +[assembly: AssemblyFileVersion("3.12.10")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index ca54f3e9..2f188f2d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: 'BSIPA-{branch}-{build}' environment: - bsipa_version: '3.12.9' + bsipa_version: '3.12.10' pull_requests: do_not_increment_build_number: true install: