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 { 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}"); } } }