You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
8.2 KiB

  1. using IllusionInjector.Logging;
  2. using SimpleJSON;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Runtime.Serialization;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. using UnityEngine.Networking;
  13. using UnityEngine;
  14. using IllusionPlugin;
  15. using System.Text.RegularExpressions;
  16. using Logger = IllusionInjector.Logging.Logger;
  17. namespace IllusionInjector.Updating
  18. {
  19. class ModUpdater : MonoBehaviour
  20. {
  21. public ModUpdater instance;
  22. public void Awake()
  23. {
  24. instance = this;
  25. CheckForUpdates();
  26. }
  27. public void CheckForUpdates()
  28. {
  29. StartCoroutine(CheckForUpdatesCoroutine());
  30. }
  31. struct UpdateCheckQueueItem
  32. {
  33. public PluginManager.BSPluginMeta Plugin;
  34. public Uri UpdateUri;
  35. public string Name;
  36. }
  37. struct UpdateQueueItem
  38. {
  39. public PluginManager.BSPluginMeta Plugin;
  40. public Uri DownloadUri;
  41. public string Name;
  42. public Version NewVersion;
  43. }
  44. private Regex commentRegex = new Regex(@"(?: \/\/.+)?$", RegexOptions.Compiled | RegexOptions.Multiline);
  45. private Dictionary<Uri, UpdateScript> cachedRequests = new Dictionary<Uri, UpdateScript>();
  46. IEnumerator CheckForUpdatesCoroutine()
  47. {
  48. Logger.log.Info("Checking for mod updates...");
  49. var toUpdate = new List<UpdateQueueItem>();
  50. var plugins = new Queue<UpdateCheckQueueItem>(PluginManager.BSMetas.Select(p => new UpdateCheckQueueItem { Plugin = p, UpdateUri = p.Plugin.UpdateUri, Name = p.Plugin.Name }));
  51. for (; plugins.Count > 0;)
  52. {
  53. var plugin = plugins.Dequeue();
  54. Logger.log.Debug($"Checking for updates for {plugin.Name}");
  55. if (plugin.UpdateUri != null)
  56. {
  57. if (!cachedRequests.ContainsKey(plugin.UpdateUri))
  58. using (var request = UnityWebRequest.Get(plugin.UpdateUri))
  59. {
  60. yield return request.SendWebRequest();
  61. if (request.isNetworkError)
  62. {
  63. Logger.log.Error("Network error while trying to update mods");
  64. Logger.log.Error(request.error);
  65. break;
  66. }
  67. if (request.isHttpError)
  68. {
  69. Logger.log.Error($"Server returned an error code while trying to update mod {plugin.Name}");
  70. Logger.log.Error(request.error);
  71. }
  72. var json = request.downloadHandler.text;
  73. json = commentRegex.Replace(json, "");
  74. JSONObject obj = null;
  75. try
  76. {
  77. obj = JSON.Parse(json).AsObject;
  78. }
  79. catch (InvalidCastException)
  80. {
  81. Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
  82. Logger.log.Error($"Response doesn't seem to be a JSON object");
  83. continue;
  84. }
  85. catch (Exception e)
  86. {
  87. Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
  88. Logger.log.Error(e);
  89. continue;
  90. }
  91. UpdateScript ss;
  92. try
  93. {
  94. ss = UpdateScript.Parse(obj);
  95. }
  96. catch (Exception e)
  97. {
  98. Logger.log.Error($"Parse error while trying to update mod {plugin.Name}");
  99. Logger.log.Error($"Script at {plugin.UpdateUri} doesn't seem to be a valid update script");
  100. Logger.log.Debug(e);
  101. continue;
  102. }
  103. cachedRequests.Add(plugin.UpdateUri, ss);
  104. }
  105. var script = cachedRequests[plugin.UpdateUri];
  106. if (script.Info.TryGetValue(plugin.Name, out UpdateScript.PluginVersionInfo info))
  107. {
  108. Logger.log.Debug($"Checking version info for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
  109. if (info.NewName != null || info.NewScript != null)
  110. plugins.Enqueue(new UpdateCheckQueueItem
  111. {
  112. Plugin = plugin.Plugin,
  113. Name = info.NewName ?? plugin.Name,
  114. UpdateUri = info.NewScript ?? plugin.UpdateUri
  115. });
  116. else
  117. {
  118. Logger.log.Debug($"New version: {info.Version}, Current version: {plugin.Plugin.Plugin.Version}");
  119. if (info.Version > plugin.Plugin.Plugin.Version)
  120. { // we should update plugin
  121. Logger.log.Debug($"Queueing update for {plugin.Name} ({plugin.Plugin.Plugin.Name})");
  122. toUpdate.Add(new UpdateQueueItem
  123. {
  124. Plugin = plugin.Plugin,
  125. DownloadUri = info.Download,
  126. Name = plugin.Name,
  127. NewVersion = info.Version
  128. });
  129. }
  130. }
  131. }
  132. else
  133. {
  134. Logger.log.Error($"Script defined for plugin {plugin.Name} doesn't define information for {plugin.Name}");
  135. continue;
  136. }
  137. }
  138. }
  139. Logger.log.Info($"{toUpdate.Count} mods need updating");
  140. if (toUpdate.Count == 0) yield break;
  141. string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetRandomFileName());
  142. Directory.CreateDirectory(tempDirectory);
  143. Logger.log.Debug($"Created temp download dirtectory {tempDirectory}");
  144. foreach (var item in toUpdate)
  145. {
  146. StartCoroutine(DownloadPluginCoroutine(tempDirectory, item));
  147. }
  148. }
  149. IEnumerator DownloadPluginCoroutine(string tempdir, UpdateQueueItem item)
  150. {
  151. var file = Path.Combine(tempdir, item.Name + ".dll");
  152. using (var req = UnityWebRequest.Get(item.DownloadUri))
  153. {
  154. req.downloadHandler = new DownloadHandlerFile(file);
  155. yield return req.SendWebRequest();
  156. if (req.isNetworkError)
  157. {
  158. Logger.log.Error($"Network error while trying to download update for {item.Plugin.Plugin.Name}");
  159. Logger.log.Error(req.error);
  160. yield break;
  161. }
  162. if (req.isHttpError)
  163. {
  164. Logger.log.Error($"Server returned an error code while trying to download update for {item.Plugin.Plugin.Name}");
  165. Logger.log.Error(req.error);
  166. yield break;
  167. }
  168. }
  169. var pluginDir = Path.GetDirectoryName(item.Plugin.Filename);
  170. var newFile = Path.Combine(pluginDir, item.Name + ".dll");
  171. File.Delete(item.Plugin.Filename);
  172. if (File.Exists(newFile))
  173. File.Delete(newFile);
  174. File.Move(file, newFile);
  175. Logger.log.Info($"{item.Plugin.Plugin.Name} updated to {item.NewVersion}");
  176. }
  177. }
  178. }