Browse Source

Updated the updater to use new ModSaber endpoints

pull/46/head
Anairkoen Schno 6 years ago
parent
commit
5cac7aaeb0
5 changed files with 117 additions and 68 deletions
  1. +2
    -2
      IPA.Injector/Properties/AssemblyInfo.cs
  2. +2
    -6
      IPA.Loader/Updating/ModsaberML/ApiEndpoint.cs
  3. +110
    -57
      IPA.Loader/Updating/ModsaberML/Updater.cs
  4. +1
    -1
      IPA.Loader/Updating/SelfPlugin.cs
  5. +2
    -2
      IPA/Properties/AssemblyInfo.cs

+ 2
- 2
IPA.Injector/Properties/AssemblyInfo.cs View File

@ -35,5 +35,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.10.4")]
[assembly: AssemblyFileVersion("3.10.4")]
[assembly: AssemblyVersion("3.10.5")]
[assembly: AssemblyFileVersion("3.10.5")]

+ 2
- 6
IPA.Loader/Updating/ModsaberML/ApiEndpoint.cs View File

@ -16,13 +16,9 @@ namespace IPA.Updating.ModsaberML
{ {
class ApiEndpoint class ApiEndpoint
{ {
#if DEBUG && UPDATETEST
public const string ApiBase = "file://Z:/Users/aaron/Source/Repos/IPA-Reloaded-BeatSaber/IPA.Tests/";
public const string GetApprovedEndpoint = "updater_test.json";
#else
public const string ApiBase = "https://www.modsaber.ml/"; public const string ApiBase = "https://www.modsaber.ml/";
public const string GetApprovedEndpoint = "registry/{0}/{1}";
#endif
public const string GetModInfoEndpoint = "registry/{0}/{1}";
public const string GetModsWithSemver = "api/v1.0/mods/semver/{0}/{1}";
class HexArrayConverter : JsonConverter class HexArrayConverter : JsonConverter
{ {


+ 110
- 57
IPA.Loader/Updating/ModsaberML/Updater.cs View File

@ -72,19 +72,17 @@ namespace IPA.Updating.ModsaberML
} }
} }
private Dictionary<string, ApiEndpoint.Mod> requestCache = new Dictionary<string, ApiEndpoint.Mod>();
private IEnumerator DownloadModInfo(string name, string ver, Ref<ApiEndpoint.Mod> result)
private Dictionary<string, string> requestCache = new Dictionary<string, string>();
private IEnumerator GetModsaberEndpoint(string url, Ref<string> result)
{ {
var uri = ApiEndpoint.ApiBase + string.Format(ApiEndpoint.GetApprovedEndpoint, name, ver);
if (requestCache.TryGetValue(uri, out ApiEndpoint.Mod value))
if (requestCache.TryGetValue(url, out string value))
{ {
result.Value = value; result.Value = value;
yield break; yield break;
} }
else else
{ {
using (var request = UnityWebRequest.Get(uri))
using (var request = UnityWebRequest.Get(ApiEndpoint.ApiBase + url))
{ {
yield return request.SendWebRequest(); yield return request.SendWebRequest();
@ -105,17 +103,69 @@ namespace IPA.Updating.ModsaberML
yield break; yield break;
} }
try
{
result.Value = JsonConvert.DeserializeObject<ApiEndpoint.Mod>(request.downloadHandler.text);
result.Value = request.downloadHandler.text;
requestCache[uri] = result.Value;
}
catch (Exception e)
{
result.Error = new Exception("Error decoding response", e);
yield break;
}
requestCache[url] = result.Value;
}
}
}
private Dictionary<string, ApiEndpoint.Mod> modCache = new Dictionary<string, ApiEndpoint.Mod>();
private IEnumerator GetModInfo(string name, string ver, Ref<ApiEndpoint.Mod> result)
{
var uri = string.Format(ApiEndpoint.GetModInfoEndpoint, Uri.EscapeUriString(name), Uri.EscapeUriString(ver));
if (modCache.TryGetValue(uri, out ApiEndpoint.Mod value))
{
result.Value = value;
yield break;
}
else
{
Ref<string> reqResult = new Ref<string>("");
yield return GetModsaberEndpoint(uri, reqResult);
try
{
result.Value = JsonConvert.DeserializeObject<ApiEndpoint.Mod>(reqResult.Value);
modCache[uri] = result.Value;
}
catch (Exception e)
{
result.Error = new Exception("Error decoding response", e);
yield break;
}
}
}
private Dictionary<string, List<ApiEndpoint.Mod>> modVersionsCache = new Dictionary<string, List<ApiEndpoint.Mod>>();
private IEnumerator GetModVersionsMatching(string name, string range, Ref<List<ApiEndpoint.Mod>> result)
{
var uri = string.Format(ApiEndpoint.GetModsWithSemver, Uri.EscapeUriString(name), Uri.EscapeUriString(range));
if (modVersionsCache.TryGetValue(uri, out List<ApiEndpoint.Mod> value))
{
result.Value = value;
yield break;
}
else
{
Ref<string> reqResult = new Ref<string>("");
yield return GetModsaberEndpoint(uri, reqResult);
try
{
result.Value = JsonConvert.DeserializeObject<List<ApiEndpoint.Mod>>(reqResult.Value);
modVersionsCache[uri] = result.Value;
}
catch (Exception e)
{
result.Error = new Exception("Error decoding response", e);
yield break;
} }
} }
} }
@ -162,8 +212,8 @@ namespace IPA.Updating.ModsaberML
var mod = new Ref<ApiEndpoint.Mod>(null); var mod = new Ref<ApiEndpoint.Mod>(null);
#region TEMPORARY get latest // SHOULD BE GREATEST OF VERSION
yield return DownloadModInfo(dep.Name, "", mod);
#region TEMPORARY get latest // SHOULD BE GREATEST OF VERSION // not going to happen because of disagreements with ModSaber
yield return GetModInfo(dep.Name, "", mod);
#endregion #endregion
try { mod.Verify(); } try { mod.Verify(); }
@ -203,36 +253,6 @@ namespace IPA.Updating.ModsaberML
private IEnumerator DependencyResolveSecondPass(Ref<List<DependencyObject>> list) private IEnumerator DependencyResolveSecondPass(Ref<List<DependencyObject>> list)
{ {
IEnumerator GetGameVersionMap(string modname, Ref<Dictionary<Version,Version>> map)
{ // gets map of mod version -> game version (2.0)
map.Value = new Dictionary<Version, Version>();
var mod = new Ref<ApiEndpoint.Mod>(null);
yield return DownloadModInfo(modname, "", mod);
try { mod.Verify(); }
catch (Exception)
{
map.Value = null;
map.Error = new Exception($"Error getting info for {modname}", mod.Error);
yield break;
}
map.Value.Add(mod.Value.Version, mod.Value.GameVersion);
foreach (var ver in mod.Value.OldVersions)
{
yield return DownloadModInfo(modname, ver.ToString(), mod);
try { mod.Verify(); }
catch (Exception e)
{
Logger.updater.Error($"Error getting info for {modname}v{ver}");
Logger.updater.Error(e);
continue;
}
map.Value.Add(mod.Value.Version, mod.Value.GameVersion);
}
}
foreach(var dep in list.Value) foreach(var dep in list.Value)
{ {
dep.Has = dep.Version != null;// dep.Version is only not null if its already installed dep.Has = dep.Version != null;// dep.Version is only not null if its already installed
@ -243,18 +263,18 @@ namespace IPA.Updating.ModsaberML
continue; continue;
} }
var dict = new Ref<Dictionary<Version, Version>>(null);
yield return GetGameVersionMap(dep.Name, dict);
try { dict.Verify(); }
var modsMatching = new Ref<List<ApiEndpoint.Mod>>(null);
yield return GetModVersionsMatching(dep.Name, dep.Requirement.ToString(), modsMatching);
try { modsMatching.Verify(); }
catch (Exception e) catch (Exception e)
{ {
Logger.updater.Error($"Error getting map for {dep.Name}");
Logger.updater.Error($"Error getting mod list for {dep.Name}");
Logger.updater.Error(e); Logger.updater.Error(e);
dep.MetaRequestFailed = true; dep.MetaRequestFailed = true;
continue; continue;
} }
var ver = dep.Requirement.MaxSatisfying(dict.Value.Where(kvp => kvp.Value == BeatSaber.GameVersion).Select(kvp => kvp.Key)); // (2.1)
var ver = modsMatching.Value.Where(val => val.GameVersion == BeatSaber.GameVersion && val.Approved).Select(mod => mod.Version).Max(); // (2.1)
if (dep.Resolved = ver != null) dep.ResolvedVersion = ver; // (2.2) if (dep.Resolved = ver != null) dep.ResolvedVersion = ver; // (2.2)
dep.Has = dep.Version == dep.ResolvedVersion && dep.Resolved; // dep.Version is only not null if its already installed dep.Has = dep.Version == dep.ResolvedVersion && dep.Resolved; // dep.Version is only not null if its already installed
} }
@ -297,7 +317,7 @@ namespace IPA.Updating.ModsaberML
Logger.updater.Debug($"Release: {BeatSaber.ReleaseType}"); Logger.updater.Debug($"Release: {BeatSaber.ReleaseType}");
var mod = new Ref<ApiEndpoint.Mod>(null); var mod = new Ref<ApiEndpoint.Mod>(null);
yield return DownloadModInfo(item.Name, item.ResolvedVersion.ToString(), mod);
yield return GetModInfo(item.Name, item.ResolvedVersion.ToString(), mod);
try { mod.Verify(); } try { mod.Verify(); }
catch (Exception e) catch (Exception e)
{ {
@ -362,6 +382,11 @@ namespace IPA.Updating.ModsaberML
if (downloadTask.IsFaulted) if (downloadTask.IsFaulted)
{ {
if (downloadTask.Exception.InnerExceptions.Where(e => e is ModsaberInterceptException).Any())
{ // any exception is an intercept exception
Logger.updater.Error($"Modsaber did not return expected data for {item.Name}");
}
Logger.updater.Error($"Error downloading mod {item.Name}"); Logger.updater.Error($"Error downloading mod {item.Name}");
Logger.updater.Error(downloadTask.Exception); Logger.updater.Error(downloadTask.Exception);
continue; continue;
@ -458,8 +483,16 @@ namespace IPA.Updating.ModsaberML
sha = new SHA1CryptoServiceProvider(); sha = new SHA1CryptoServiceProvider();
var fileHash = sha.ComputeHash(ostream); var fileHash = sha.ComputeHash(ostream);
if (!LoneFunctions.UnsafeCompare(fileHash, fileInfo.FileHashes[entry.FileName]))
throw new Exception("The hash for the file doesn't match what is defined");
try
{
if (!LoneFunctions.UnsafeCompare(fileHash, fileInfo.FileHashes[entry.FileName]))
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!");
}
ostream.Seek(0, SeekOrigin.Begin); ostream.Seek(0, SeekOrigin.Begin);
FileInfo targetFile = new FileInfo(Path.Combine(Environment.CurrentDirectory, entry.FileName)); FileInfo targetFile = new FileInfo(Path.Combine(Environment.CurrentDirectory, entry.FileName));
@ -530,5 +563,25 @@ namespace IPA.Updating.ModsaberML
{ {
} }
} }
[Serializable]
internal class ModsaberInterceptException : Exception
{
public ModsaberInterceptException()
{
}
public ModsaberInterceptException(string message) : base(message)
{
}
public ModsaberInterceptException(string message, Exception innerException) : base(message, innerException)
{
}
protected ModsaberInterceptException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
} }

+ 1
- 1
IPA.Loader/Updating/SelfPlugin.cs View File

@ -11,7 +11,7 @@ namespace IPA.Updating
internal class SelfPlugin : IBeatSaberPlugin internal class SelfPlugin : IBeatSaberPlugin
{ {
internal const string IPA_Name = "Beat Saber IPA"; internal const string IPA_Name = "Beat Saber IPA";
internal const string IPA_Version = "3.10.4";
internal const string IPA_Version = "3.10.5";
public static SelfPlugin Instance { get; set; } = new SelfPlugin(); public static SelfPlugin Instance { get; set; } = new SelfPlugin();


+ 2
- 2
IPA/Properties/AssemblyInfo.cs View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.10.4")]
[assembly: AssemblyFileVersion("3.10.4")]
[assembly: AssemblyVersion("3.10.5")]
[assembly: AssemblyFileVersion("3.10.5")]

Loading…
Cancel
Save