diff --git a/IPA/Properties/AssemblyInfo.cs b/IPA/Properties/AssemblyInfo.cs
index 8f4ac1df..85e4516e 100644
--- a/IPA/Properties/AssemblyInfo.cs
+++ b/IPA/Properties/AssemblyInfo.cs
@@ -32,5 +32,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.8.1")]
-[assembly: AssemblyFileVersion("3.8.1")]
+[assembly: AssemblyVersion("3.8.2")]
+[assembly: AssemblyFileVersion("3.8.2")]
diff --git a/IllusionInjector/IllusionInjector.csproj b/IllusionInjector/IllusionInjector.csproj
index 7aa1a08f..9063611c 100644
--- a/IllusionInjector/IllusionInjector.csproj
+++ b/IllusionInjector/IllusionInjector.csproj
@@ -78,10 +78,10 @@
+
-
-
+
diff --git a/IllusionInjector/PluginManager.cs b/IllusionInjector/PluginManager.cs
index c907cb1f..88dfccec 100644
--- a/IllusionInjector/PluginManager.cs
+++ b/IllusionInjector/PluginManager.cs
@@ -1,4 +1,5 @@
using IllusionInjector.Logging;
+using IllusionInjector.Updating;
using IllusionInjector.Utilities;
using IllusionPlugin;
using IllusionPlugin.BeatSaber;
@@ -100,6 +101,15 @@ namespace IllusionInjector
File.Copy(Path.Combine(pluginDirectory, s), pluginCopy);
}
+ var selfPlugin = new BSPluginMeta
+ {
+ Filename = Path.Combine(Environment.CurrentDirectory, "IPA.exe"),
+ Plugin = new SelfPlugin()
+ };
+ selfPlugin.ModsaberInfo = selfPlugin.Plugin.ModInfo;
+
+ _bsPlugins.Add(selfPlugin);
+
//Load copied plugins
string[] copiedPlugins = Directory.GetFiles(cacheDir, "*.dll");
foreach (string s in copiedPlugins)
diff --git a/IllusionInjector/Updating/ModsaberML/Updater.cs b/IllusionInjector/Updating/ModsaberML/Updater.cs
index 46fb0a09..6982322e 100644
--- a/IllusionInjector/Updating/ModsaberML/Updater.cs
+++ b/IllusionInjector/Updating/ModsaberML/Updater.cs
@@ -1,9 +1,11 @@
-using IllusionInjector.Utilities;
+using IllusionInjector.Updating.Backup;
+using IllusionInjector.Utilities;
using Ionic.Zip;
using SimpleJSON;
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
@@ -143,9 +145,11 @@ namespace IllusionInjector.Updating.ModsaberML
if (toUpdate.Count == 0) yield break;
+ string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetRandomFileName());
+ Directory.CreateDirectory(tempDirectory);
foreach (var item in toUpdate)
{
- StartCoroutine(UpdateModCoroutine(item));
+ StartCoroutine(UpdateModCoroutine(item, tempDirectory));
}
}
@@ -195,7 +199,7 @@ namespace IllusionInjector.Updating.ModsaberML
}
}
- private void ExtractPluginAsync(MemoryStream stream, UpdateStruct item, ApiEndpoint.Mod.PlatformFile fileInfo)
+ private void ExtractPluginAsync(MemoryStream stream, UpdateStruct item, ApiEndpoint.Mod.PlatformFile fileInfo, string tempDirectory)
{
Logger.log.Debug($"Extracting ZIP file for {item.plugin.Plugin.Name}");
//var stream = await httpClient.GetStreamAsync(url);
@@ -206,47 +210,83 @@ namespace IllusionInjector.Updating.ModsaberML
if (!LoneFunctions.UnsafeCompare(hash, fileInfo.Hash))
throw new Exception("The hash for the file doesn't match what is defined");
- using (var zipFile = ZipFile.Read(stream))
+ var newFiles = new List();
+ var backup = new BackupUnit(tempDirectory, $"backup-{item.plugin.ModsaberInfo.InternalName}");
+
+ try
{
- Logger.log.Debug("Streams opened");
- foreach (var entry in zipFile)
+ bool shouldDeleteOldFile = true;
+
+ using (var zipFile = ZipFile.Read(stream))
{
- if (entry.IsDirectory)
- {
- Logger.log.Debug($"Creating directory {entry.FileName}");
- Directory.CreateDirectory(Path.Combine(Environment.CurrentDirectory, entry.FileName));
- }
- else
+ Logger.log.Debug("Streams opened");
+ foreach (var entry in zipFile)
{
- using (var ostream = new MemoryStream((int)entry.UncompressedSize))
+ if (entry.IsDirectory)
{
- entry.Extract(ostream);
- ostream.Seek(0, SeekOrigin.Begin);
+ Logger.log.Debug($"Creating directory {entry.FileName}");
+ Directory.CreateDirectory(Path.Combine(Environment.CurrentDirectory, entry.FileName));
+ }
+ else
+ {
+ using (var ostream = new MemoryStream((int)entry.UncompressedSize))
+ {
+ entry.Extract(ostream);
+ ostream.Seek(0, SeekOrigin.Begin);
- sha = new SHA1CryptoServiceProvider();
- 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");
+ sha = new SHA1CryptoServiceProvider();
+ 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");
- ostream.Seek(0, SeekOrigin.Begin);
- FileInfo targetFile = new FileInfo(Path.Combine(Environment.CurrentDirectory, entry.FileName));
- if (targetFile.Exists)
- {
- }
+ ostream.Seek(0, SeekOrigin.Begin);
+ FileInfo targetFile = new FileInfo(Path.Combine(Environment.CurrentDirectory, entry.FileName));
+
+ if (targetFile.FullName == item.plugin.Filename)
+ shouldDeleteOldFile = false; // overwriting old file, no need to delete
+
+ if (targetFile.Exists)
+ backup.Add(targetFile);
+ else
+ newFiles.Add(targetFile);
- Logger.log.Debug($"Extracting file {targetFile.FullName}");
+ Logger.log.Debug($"Extracting file {targetFile.FullName}");
- var fstream = targetFile.Create();
- ostream.CopyTo(fstream);
+ var fstream = targetFile.Create();
+ ostream.CopyTo(fstream);
+ }
}
}
}
+
+ if (item.plugin.Plugin is SelfPlugin)
+ { // currently updating self
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = item.plugin.Filename,
+ Arguments = $"--waitfor={Process.GetCurrentProcess().Id} --nowait",
+ UseShellExecute = false
+ });
+ }
+ else if (shouldDeleteOldFile)
+ File.Delete(item.plugin.Filename);
}
+ catch (Exception)
+ { // something failed; restore
+ foreach (var file in newFiles)
+ file.Delete();
+ backup.Restore();
+ backup.Delete();
+
+ throw;
+ }
+
+ backup.Delete();
Logger.log.Debug("Downloader exited");
}
- IEnumerator UpdateModCoroutine(UpdateStruct item)
+ IEnumerator UpdateModCoroutine(UpdateStruct item, string tempDirectory)
{
ApiEndpoint.Mod.PlatformFile platformFile;
if (SteamCheck.IsAvailable || item.externInfo.OculusFile == null)
@@ -296,7 +336,7 @@ namespace IllusionInjector.Updating.ModsaberML
var downloadTask = Task.Run(() =>
{ // use slightly more multithreaded approach than coroutines
- ExtractPluginAsync(stream, item, platformFile);
+ ExtractPluginAsync(stream, item, platformFile, tempDirectory);
}, taskTokenSource.Token);
while (!(downloadTask.IsCompleted || downloadTask.IsCanceled || downloadTask.IsFaulted))
diff --git a/IllusionInjector/Updating/Old/ModUpdater.cs b/IllusionInjector/Updating/Old/ModUpdater.cs
deleted file mode 100644
index 99ab7409..00000000
--- a/IllusionInjector/Updating/Old/ModUpdater.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-using IllusionInjector.Logging;
-using SimpleJSON;
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
-using UnityEngine.Networking;
-using UnityEngine;
-using IllusionPlugin;
-using System.Text.RegularExpressions;
-using Logger = IllusionInjector.Logging.Logger;
-
-namespace IllusionInjector.Updating
-{
-#if OLD_UPDATER
- class ModUpdater : MonoBehaviour
- {
- public ModUpdater instance;
-
- public void Awake()
- {
- instance = this;
- CheckForUpdates();
- }
-
- public void CheckForUpdates()
- {
- StartCoroutine(CheckForUpdatesCoroutine());
- }
-
- struct UpdateCheckQueueItem
- {
- public PluginManager.BSPluginMeta Plugin;
- public Uri UpdateUri;
- public string Name;
- }
-
- struct UpdateQueueItem
- {
- public PluginManager.BSPluginMeta Plugin;
- public Uri DownloadUri;
- public string Name;
- public Version NewVersion;
- }
-
- private Regex commentRegex = new Regex(@"(?: \/\/.+)?$", RegexOptions.Compiled | RegexOptions.Multiline);
- private Dictionary cachedRequests = new Dictionary();
- IEnumerator CheckForUpdatesCoroutine()
- {
- Logger.log.Info("Checking for mod updates...");
-
- var toUpdate = new List();
- var plugins = new Queue(PluginManager.BSMetas.Select(p => new UpdateCheckQueueItem { Plugin = p, UpdateUri = p.Plugin.UpdateUri, Name = p.Plugin.Name }));
-
- for (; plugins.Count > 0;)
- {
- var plugin = plugins.Dequeue();
-
- Logger.log.Debug($"Checking for updates for {plugin.Name}");
-
- if (plugin.UpdateUri != null)
- {
- if (!cachedRequests.ContainsKey(plugin.UpdateUri))
- using (var request = UnityWebRequest.Get(plugin.UpdateUri))
- {
- yield return request.SendWebRequest();
-
- if (request.isNetworkError)
- {
- Logger.log.Error("Network error while trying to update mods");
- Logger.log.Error(request.error);
- break;
- }
- if (request.isHttpError)
- {
- Logger.log.Error($"Server returned an error code while trying to update mod {plugin.Name}");
- Logger.log.Error(request.error);
- }
-
- var json = request.downloadHandler.text;
-
- json = commentRegex.Replace(json, "");
-
- JSONObject obj = null;
- try
- {
- obj = JSON.Parse(json).AsObject;
- }
- catch (InvalidCastException)
- {
- Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
- Logger.log.Error($"Response doesn't seem to be a JSON object");
- continue;
- }
- catch (Exception e)
- {
- Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
- Logger.log.Error(e);
- continue;
- }
-
- UpdateScript ss;
- try
- {
- ss = UpdateScript.Parse(obj);
- }
- catch (Exception e)
- {
- Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
- Logger.log.Error($"Script at {plugin.UpdateUri} doesn't seem to be a valid update script");
- Logger.log.Debug(e);
- continue;
- }
-
- cachedRequests.Add(plugin.UpdateUri, ss);
- }
-
- var script = cachedRequests[plugin.UpdateUri];
- if (script.Info.TryGetValue(plugin.Name, out UpdateScript.PluginVersionInfo info))
- {
- Logger.log.Debug($"Checking version info for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
- if (info.NewName != null || info.NewScript != null)
- plugins.Enqueue(new UpdateCheckQueueItem
- {
- Plugin = plugin.Plugin,
- Name = info.NewName ?? plugin.Name,
- UpdateUri = info.NewScript ?? plugin.UpdateUri
- });
- else
- {
- Logger.log.Debug($"New version: {info.Version}, Current version: {plugin.Plugin.Plugin.Version}");
- if (info.Version > plugin.Plugin.Plugin.Version)
- { // we should update plugin
- Logger.log.Debug($"Queueing update for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
-
- toUpdate.Add(new UpdateQueueItem
- {
- Plugin = plugin.Plugin,
- DownloadUri = info.Download,
- Name = plugin.Name,
- NewVersion = info.Version
- });
- }
- }
- }
- else
- {
- Logger.log.Error($"Script defined for plugin {plugin.Name} doesn't define information for {plugin.Name}");
- continue;
- }
- }
- }
-
- Logger.log.Info($"{toUpdate.Count} mods need updating");
-
- if (toUpdate.Count == 0) yield break;
-
- string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetRandomFileName());
- Directory.CreateDirectory(tempDirectory);
- Logger.log.Debug($"Created temp download dirtectory {tempDirectory}");
- foreach (var item in toUpdate)
- {
- StartCoroutine(DownloadPluginCoroutine(tempDirectory, item));
- }
- }
-
- IEnumerator DownloadPluginCoroutine(string tempdir, UpdateQueueItem item)
- {
- var file = Path.Combine(tempdir, item.Name + ".dll");
-
- using (var req = UnityWebRequest.Get(item.DownloadUri))
- {
- req.downloadHandler = new DownloadHandlerFile(file);
- yield return req.SendWebRequest();
-
- if (req.isNetworkError)
- {
- Logger.log.Error($"Network error while trying to download update for {item.Plugin.Plugin.Name}");
- Logger.log.Error(req.error);
- yield break;
- }
- if (req.isHttpError)
- {
- Logger.log.Error($"Server returned an error code while trying to download update for {item.Plugin.Plugin.Name}");
- Logger.log.Error(req.error);
- yield break;
- }
- }
-
- var pluginDir = Path.GetDirectoryName(item.Plugin.Filename);
- var newFile = Path.Combine(pluginDir, item.Name + ".dll");
-
- File.Delete(item.Plugin.Filename);
- if (File.Exists(newFile))
- File.Delete(newFile);
- File.Move(file, newFile);
-
- Logger.log.Info($"{item.Plugin.Plugin.Name} updated to {item.NewVersion}");
- }
- }
-#endif
-}
diff --git a/IllusionInjector/Updating/Old/UpdateScript.cs b/IllusionInjector/Updating/Old/UpdateScript.cs
deleted file mode 100644
index e133b676..00000000
--- a/IllusionInjector/Updating/Old/UpdateScript.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using SimpleJSON;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace IllusionInjector.Updating
-{
- /** // JSON format
- * {
- * "_updateScript": "0.1", // version
- * "": { // an entry for your plugin, using its annotated name
- * "version": "", // required, should be in .NET Version class format
- * // note: only required if neither newName nor newScript is specified
- * "newName": "", // optional, defines a new name for the plugin (gets saved under this name)
- * // (updater will also check this file for this name to get latest)
- * "newScript": "", // optional, defines a new location for the update script
- * // updater will look here for latest version too
- * // note: if both newName and newScript are defined, the updater will only look in newScript
- * // for newName, and not any other combination
- * "download": "", // required, defines URL to use for downloading new version
- * // note: only required if neither newName nor newScript is specified
- * },
- * ...
- * }
- */
-
- class UpdateScript
- {
- static readonly Version ScriptVersion = new Version(0, 1);
-
- public Version Version { get; private set; }
-
- private Dictionary info = new Dictionary();
- public IReadOnlyDictionary Info { get => info; }
-
- public class PluginVersionInfo
- {
- public Version Version { get; protected internal set; }
- public string NewName { get; protected internal set; }
- public Uri NewScript { get; protected internal set; }
- public Uri Download { get; protected internal set; }
- }
-
- public static UpdateScript Parse(JSONObject jscript)
- {
- var script = new UpdateScript
- {
- Version = Version.Parse(jscript["_updateScript"].Value)
- };
- if (script.Version != ScriptVersion)
- throw new UpdateScriptParseException("Script version mismatch");
-
- jscript.Remove("_updateScript");
-
- foreach (var kvp in jscript)
- {
- var obj = kvp.Value.AsObject;
- var pvi = new PluginVersionInfo
- {
- Version = obj.Linq.Any(p => p.Key == "version") ? Version.Parse(obj["version"].Value) : null,
- Download = obj.Linq.Any(p => p.Key == "download") ? new Uri(obj["download"].Value) : null,
-
- NewName = obj.Linq.Any(p => p.Key == "newName") ? obj["newName"] : null,
- NewScript = obj.Linq.Any(p => p.Key == "newScript") ? new Uri(obj["newScript"]) : null
- };
- if (pvi.NewName == null && pvi.NewScript == null && (pvi.Version == null || pvi.Download == null))
- throw new UpdateScriptParseException($"Required fields missing from object {kvp.Key}");
-
- script.info.Add(kvp.Key, pvi);
- }
-
- return script;
- }
-
- [Serializable]
- private class UpdateScriptParseException : Exception
- {
- public UpdateScriptParseException()
- {
- }
-
- public UpdateScriptParseException(string message) : base(message)
- {
- }
-
- public UpdateScriptParseException(string message, Exception innerException) : base(message, innerException)
- {
- }
-
- protected UpdateScriptParseException(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- }
- }
- }
-}
diff --git a/IllusionInjector/Updating/SelfPlugin.cs b/IllusionInjector/Updating/SelfPlugin.cs
new file mode 100644
index 00000000..6b6b1b28
--- /dev/null
+++ b/IllusionInjector/Updating/SelfPlugin.cs
@@ -0,0 +1,55 @@
+using IllusionPlugin;
+using IllusionPlugin.BeatSaber;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine.SceneManagement;
+
+namespace IllusionInjector.Updating
+{
+ internal class SelfPlugin : IBeatSaberPlugin
+ {
+ internal const string IPA_Name = "Beat Saber IPA";
+ internal const string IPA_Version = "3.8.2";
+
+ public string Name => IPA_Name;
+
+ public string Version => IPA_Version;
+
+ public ModsaberModInfo ModInfo => new ModsaberModInfo
+ {
+ CurrentVersion = new Version(IPA_Version),
+ InternalName = "beatsaber-ipa-reloaded"
+ };
+
+ public void OnActiveSceneChanged(Scene prevScene, Scene nextScene)
+ {
+ }
+
+ public void OnApplicationQuit()
+ {
+ }
+
+ public void OnApplicationStart()
+ {
+ }
+
+ public void OnFixedUpdate()
+ {
+ }
+
+ public void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
+ {
+ }
+
+ public void OnSceneUnloaded(Scene scene)
+ {
+ }
+
+ public void OnUpdate()
+ {
+ }
+ }
+}
diff --git a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache
index 264d2538..3de3543e 100644
--- a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache
+++ b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache
@@ -1 +1 @@
-4fc156f3cea4b1f659732d784ceeb2b98e58bc07
+f6d2b3a83d839d28f9c6280f831d88c7c7de66f4
diff --git a/IllusionPlugin/IllusionPlugin.csproj b/IllusionPlugin/IllusionPlugin.csproj
index 9aa220d7..8375b907 100644
--- a/IllusionPlugin/IllusionPlugin.csproj
+++ b/IllusionPlugin/IllusionPlugin.csproj
@@ -40,6 +40,7 @@
..\Libs\UnityEngine.CoreModule.dll
+ False