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.

233 lines
9.9KB

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