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.

152 lines
7.0KB

  1. using GCMM.Properties;
  2. using Newtonsoft.Json;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.IO.Compression;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using System.Windows.Forms;
  11. namespace GCMM
  12. {
  13. partial class MainForm
  14. {
  15. public async Task InstallMod(ModInfo mod)
  16. {
  17. if (mod.DownloadURL == null) return;
  18. if (CheckNoExe())
  19. return;
  20. if (mod.Name != "GamecraftModdingAPI")
  21. await UpdateAPI();
  22. var tmp = Directory.CreateDirectory("temp");
  23. var plugins = Directory.CreateDirectory(GamePath(@"\Plugins"));
  24. string tmppath = tmp.FullName + Path.DirectorySeparatorChar + mod.Name;
  25. using (var client = GetClient())
  26. {
  27. await client.DownloadFileTaskAsync(mod.DownloadURL, tmppath);
  28. string disposition = client.ResponseHeaders["Content-Disposition"];
  29. string filename = disposition.Substring(disposition.IndexOf("filename=") + 10).Replace("\"", "");
  30. if (filename.EndsWith(".dll"))
  31. File.Move(tmppath, plugins.FullName + Path.DirectorySeparatorChar + mod.Name + ".dll"); //Force mod name to make uninstalls & identifying easier
  32. else if (filename.EndsWith(".zip"))
  33. {
  34. bool pluginOnly = true;
  35. using (var archive = ZipFile.OpenRead(tmppath))
  36. {
  37. bool modFound = false;
  38. foreach (var entry in archive.Entries)
  39. {
  40. if (entry.FullName == "Plugins/")
  41. pluginOnly = false;
  42. if (entry.FullName == "Plugins/" + mod.Name + ".dll")
  43. {
  44. modFound = true;
  45. pluginOnly = false; //The directory-only entry may be missing
  46. }
  47. else if (pluginOnly && entry.FullName == mod.Name + ".dll")
  48. modFound = true;
  49. if (!pluginOnly && modFound) break;
  50. }
  51. if (!modFound)
  52. if (MessageBox.Show("The mod was not found in the downloaded archive. It likely means it's using a different name for the dll file. The mod manager will not be able to track this mod if you continue. Do you want to continue?", "Mod not found in archive", MessageBoxButtons.YesNo) == DialogResult.No)
  53. return;
  54. ExtractMod(archive, pluginOnly ? plugins.FullName : Settings.Default.GamePath, mod);
  55. }
  56. File.Delete(tmppath);
  57. }
  58. else
  59. {
  60. MessageBox.Show("Don't know how to install file: " + filename + "\nThe file remains in the temp folder near the mod manager");
  61. return;
  62. }
  63. GetInstalledMods(); //Update list
  64. }
  65. }
  66. public void ExtractMod(ZipArchive archive, string destinationDirectoryName, ModInfo mod)
  67. {
  68. LoadFileList(mod);
  69. DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
  70. string destinationDirectoryFullPath = di.FullName;
  71. foreach (ZipArchiveEntry file in archive.Entries)
  72. {
  73. string completeFileName = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, file.FullName));
  74. if (!completeFileName.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
  75. {
  76. throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability");
  77. }
  78. Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); //Sometimes there are no directory-only entries
  79. if (file.Name == "")
  80. {// Assuming Empty for Directory
  81. continue;
  82. }
  83. if ((mod.Version == null || mod.ModFiles.Count != 0) //Negated: The mod is installed and we don't know about any of its files
  84. && File.Exists(completeFileName) // OR it's a new file
  85. && !mod.ModFiles.Contains(completeFileName) // OR it's known to be part of the mod already
  86. && file.FullName != "Plugins/" + mod.Name + ".dll") // OR it's the plugin's DLL (dll->zip release)
  87. {
  88. if (MessageBox.Show("The mod zip contains a file that exists as part of the game. Do you want to skip this file?\n" + file.FullName + "\nOnly choose No if it's part of a previous installation of the mod.", "File is part of the game", MessageBoxButtons.YesNo) == DialogResult.Yes)
  89. continue;
  90. }
  91. mod.ModFiles.Add(completeFileName);
  92. file.ExtractToFile(completeFileName, true);
  93. }
  94. SaveFileList(mod);
  95. }
  96. public void SaveFileList(ModInfo mod)
  97. {
  98. if (mod.ModFiles != null)
  99. File.WriteAllText(mod.Name + ".json", JsonConvert.SerializeObject(mod.ModFiles));
  100. }
  101. public void LoadFileList(ModInfo mod)
  102. {
  103. if (File.Exists(mod.Name + ".json"))
  104. mod.ModFiles = JsonConvert.DeserializeObject<HashSet<string>>(File.ReadAllText(mod.Name + ".json"));
  105. else
  106. mod.ModFiles = new HashSet<string>();
  107. }
  108. public void UninstallMod(ModInfo mod)
  109. {
  110. LoadFileList(mod);
  111. if (mod.ModFiles.Count == 0) //A single DLL
  112. File.Delete(GamePath(@"\Plugins\" + mod.Name + ".dll"));
  113. else //A ZIP
  114. {
  115. foreach (string file in mod.ModFiles)
  116. {
  117. File.Delete(file);
  118. var parent = Directory.GetParent(file);
  119. if (!parent.EnumerateFiles().Any())
  120. parent.Delete(); //May delete the Plugins dir if empty
  121. }
  122. }
  123. File.Delete(mod.Name + ".json");
  124. mod.Version = null; //Not installed
  125. AddUpdateModInList(mod); //Update list
  126. }
  127. public async Task UpdateAPI()
  128. {
  129. if (!mods.ContainsKey("GamecraftModdingAPI"))
  130. return;
  131. var gcmapi = mods["GamecraftModdingAPI"];
  132. if (!gcmapi.Installed || gcmapi.Updatable)
  133. {
  134. if (MessageBox.Show($"GamecraftModdingAPI will be {(gcmapi.Installed ? "updated" : "installed")} as most mods need it to work. You can uninstall it if you're sure you don't need it.", "API needed", MessageBoxButtons.OKCancel)
  135. == DialogResult.OK)
  136. await InstallMod(gcmapi);
  137. }
  138. }
  139. }
  140. }