Techblox Mod Manager / Launcher
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.

215 lines
9.2KB

  1. using GCMM.Properties;
  2. using Newtonsoft.Json.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Reflection;
  11. using System.Runtime.CompilerServices;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using System.Windows.Forms;
  15. namespace GCMM
  16. {
  17. partial class MainForm
  18. {
  19. public HashSet<string> GetInstalledMods()
  20. {
  21. bool disabled = false;
  22. if (!Directory.Exists(GamePath("\\Plugins")))
  23. if (Directory.Exists(GamePath("\\Plugins_Disabled")))
  24. disabled = true;
  25. else return new HashSet<string>();
  26. var installed = new HashSet<string>();
  27. foreach (var modPath in Directory.GetFiles(GamePath(disabled ? @"\Plugins_Disabled" : @"\Plugins"), "*.dll"))
  28. {
  29. try
  30. {
  31. var an = AssemblyName.GetAssemblyName(modPath);
  32. if (an.Name == "0Harmony") continue;
  33. //Use filename to avoid differences between repository & assembly name casing
  34. var mod = new ModInfo { Name = Path.GetFileNameWithoutExtension(modPath), Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) };
  35. AddUpdateModInList(mod);
  36. installed.Add(mod.Name);
  37. }
  38. catch (BadImageFormatException)
  39. { //Not a .NET assembly
  40. }
  41. }
  42. try
  43. {
  44. string ipath = GamePath("\\IPA.exe"); //Heh
  45. if (File.Exists(ipath))
  46. {
  47. var an = AssemblyName.GetAssemblyName(ipath);
  48. gcipa.Version = an.Version;
  49. gcipa.LastUpdated = File.GetLastWriteTime(ipath);
  50. }
  51. }
  52. catch (BadImageFormatException)
  53. { //Not a .NET assembly
  54. }
  55. try
  56. {
  57. string mmpath = "GCMM.exe";
  58. if (File.Exists(mmpath))
  59. {
  60. var an = AssemblyName.GetAssemblyName(mmpath);
  61. gcmm.Version = an.Version;
  62. gcmm.LastUpdated = File.GetLastWriteTime(mmpath);
  63. }
  64. }
  65. catch (BadImageFormatException)
  66. { //Not a .NET assembly
  67. }
  68. return installed;
  69. }
  70. public async Task GetAvailableMods()
  71. {
  72. bool preview = GetExe()?.Contains("Preview") ?? false;
  73. using (var client = GetClient())
  74. {
  75. string str = await client.DownloadStringTaskAsync("https://exmods.org/mods/modlist.tsv");
  76. foreach (string line in str.Trim().Split('\n'))
  77. {
  78. var sp = line.Split('\t');
  79. if (sp.Length < 2) continue;
  80. var mod = new ModInfo
  81. {
  82. Author = sp[0].Trim(),
  83. Name = sp[1].Trim()
  84. };
  85. if (await FetchModInfo(mod, preview, true)) //If it's actually a mod
  86. AddUpdateModInList(mod);
  87. }
  88. }
  89. if (gcmm.LatestVersion == null) //Only check once
  90. {
  91. await FetchModInfo(gcipa, preview, false);
  92. await FetchModInfo(gcmm, preview, false);
  93. if (gcmm.Updatable)
  94. if (MessageBox.Show("There is a GCMM update available! Do you want to download it now? If yes, extract it over this installation.\n\n" + gcmm.UpdateDetails, "Mod Manager update", MessageBoxButtons.YesNo)
  95. == DialogResult.Yes)
  96. Process.Start(gcmm.DownloadURL);
  97. }
  98. }
  99. public async Task<bool> FetchModInfo(ModInfo mod, bool preview, bool desc)
  100. {
  101. string repoURL = "/api/v1/repos/" + mod.Author + "/" + mod.Name + "/releases";
  102. using (var client = GetClient())
  103. {
  104. var arr = JArray.Parse(await client.DownloadStringTaskAsync(repoURL));
  105. var release = arr.FirstOrDefault(rel =>
  106. {
  107. if ((bool) rel["prerelease"] || (bool) rel["draft"])
  108. return false;
  109. var vs = rel["tag_name"].ToString();
  110. int ind = vs.IndexOf('-');
  111. if (ind != -1)
  112. {
  113. if (vs.Substring(ind + 1).Equals("preview", StringComparison.InvariantCultureIgnoreCase)
  114. && !preview)
  115. return false;
  116. }
  117. return true;
  118. });
  119. if (release == null)
  120. return false;
  121. var verstr = release["tag_name"].ToString().Replace("v", "");
  122. int index = verstr.IndexOf('-');
  123. if (index != -1)
  124. verstr = verstr.Substring(0, index);
  125. JToken asset;
  126. if (release["assets"].Count() == 1)
  127. asset = release["assets"].First;
  128. else
  129. asset = release["assets"].FirstOrDefault(token =>
  130. {
  131. string name = token["name"].ToString();
  132. return name == mod.Name + ".dll" || name == mod.Name + ".zip";
  133. });
  134. mod.DownloadURL = asset?["browser_download_url"]?.ToString();
  135. mod.LastUpdated = (DateTime)release["published_at"];
  136. var ver = verstr.Split('.').Select(str => int.Parse(str)).ToArray();
  137. int getver(byte i) => ver.Length > i ? ver[i] : 0; //By default it sets values not present to -1, but we need them to be 0
  138. mod.LatestVersion = new Version(getver(0), getver(1), getver(2), getver(3));
  139. mod.UpdateDetails = release["name"] + "\n\n" + release["body"].ToString();
  140. if (desc)
  141. {
  142. try
  143. {
  144. var obj = JObject.Parse(await client.DownloadStringTaskAsync("/api/v1/repos/" + mod.Author + "/" + mod.Name + "/contents/README.md"));
  145. mod.Description = Encoding.UTF8.GetString(Convert.FromBase64String(obj["content"].ToString()));
  146. }
  147. catch (WebException)
  148. { //It returns a HTTP 500 if it doesn't exist...
  149. }
  150. }
  151. return true;
  152. }
  153. }
  154. public void AddUpdateModInList(ModInfo mod)
  155. {
  156. if (mods.ContainsKey(mod.Name) ^ modlist.Items.ContainsKey(mod.Name)) //The ListView's keys aren't case sensitive
  157. throw new InvalidOperationException("The mod isn't present in one of the two places: " + mod.Name);
  158. ListViewItem item;
  159. if (modlist.Items.ContainsKey(mod.Name))
  160. {
  161. var omod = mods[mod.Name];
  162. item = modlist.Items[mod.Name];
  163. var items = item.SubItems;
  164. omod.Author = mod.Author ?? omod.Author;
  165. omod.Version = mod.Version ?? omod.Version; //If the object comes from the dictionary then it's directly modified (uninstall)
  166. omod.LatestVersion = mod.LatestVersion ?? omod.LatestVersion;
  167. omod.LastUpdated = mod.LastUpdated == default ? omod.LastUpdated : mod.LastUpdated;
  168. omod.Description = mod.Description ?? omod.Description;
  169. omod.DownloadURL = mod.DownloadURL ?? omod.DownloadURL;
  170. omod.UpdateDetails = mod.UpdateDetails ?? omod.UpdateDetails;
  171. items[1].Text = omod.Author ?? "";
  172. items[2].Text = (omod.Version ?? omod.LatestVersion)?.ToString();
  173. items[3].Text = omod.LastUpdated.ToString();
  174. item.Group = omod.Installed ? modlist.Groups["installed"] : modlist.Groups["available"];
  175. modlist.Sort();
  176. mod = omod;
  177. }
  178. else
  179. {
  180. mods.Add(mod.Name, mod);
  181. item = new ListViewItem(new[] { mod.Name, mod.Author ?? "", (mod.Version ?? mod.LatestVersion)?.ToString() ?? "", mod.LastUpdated.ToString() }, modlist.Groups[mod.Installed ? "installed" : "available"]);
  182. item.Name = mod.Name;
  183. modlist.Items.Add(item);
  184. }
  185. if (mod.LatestVersion != null && mod.Version != null && mod.Version < mod.LatestVersion)
  186. item.ForeColor = Color.Blue;
  187. else
  188. item.ForeColor = modlist.ForeColor;
  189. }
  190. public void CheckUninstalledMods(HashSet<string> installed)
  191. {
  192. List<string> delete = new List<string>();
  193. foreach (string name in mods.Keys.Except(installed))
  194. {
  195. var mod = mods[name];
  196. mod.Version = null;
  197. if (mod.Author != null)
  198. AddUpdateModInList(mod);
  199. else
  200. delete.Add(name);
  201. }
  202. delete.ForEach(name => { mods.Remove(name); modlist.Items[name].Remove(); });
  203. }
  204. }
  205. }