Downloader.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. using Microsoft.Extensions.Logging;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.IO;
  6. using System.IO.Compression;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Http.Handlers;
  11. using System.Text.Json;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. using VeloeMinecraftLauncher.Entity.Assets;
  15. using VeloeMinecraftLauncher.Models.Entity;
  16. namespace VeloeMinecraftLauncher.Utils;
  17. internal class Downloader
  18. {
  19. static HttpClient _sHttpClient = new();
  20. public DownloaderParameters Parameters { get; private set; }
  21. private readonly Serilog.ILogger _logger;
  22. public Downloader(DownloaderParameters parameters, Serilog.ILogger logger)
  23. {
  24. Parameters = parameters;
  25. _logger = logger;
  26. }
  27. public async Task DownloadClient(
  28. CancellationToken cancellationToken = default)
  29. {
  30. try
  31. {
  32. //task 1 - download java
  33. //task 2 - download jar
  34. //task 3 - download libs
  35. //task 4 - download assets
  36. //task 5 - download addons
  37. //task 6 - download modpacks
  38. Parameters.Action.IsControlsEnabled(false);
  39. Parameters.Action.SetProgress(0);
  40. var tasksCount = 3;
  41. var tasksIterator = 0;
  42. if (Parameters.Parameter.DownloadJava) tasksCount++;
  43. if (Parameters.Parameter.InstallForge || Parameters.Parameter.InstallOptifine || Parameters.Parameter.InstallForgeOptifine || Parameters.Parameter.InstallFabric) tasksCount++;
  44. if (Parameters.Data.SelectedModpack is not null) tasksCount++;
  45. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  46. var handler = new HttpClientHandler() { AllowAutoRedirect = true };
  47. var ph = new ProgressMessageHandler(handler);
  48. ph.HttpSendProgress += (_, args) =>
  49. {
  50. Parameters.Action.SetProgress(args.ProgressPercentage);
  51. };
  52. ph.HttpReceiveProgress += (_, args) =>
  53. {
  54. Parameters.Action.SetProgress(args.ProgressPercentage);
  55. };
  56. using var httpClient = new HttpClient(ph);
  57. //downloading json
  58. Entity.Version.Version? versionJson = Parameters.Data.VersionJson ?? await GetVersionJson();
  59. ArgumentNullException.ThrowIfNull(versionJson);
  60. //SetMaxProgressBarValue(size);
  61. _logger.Debug("Downloading minecraft {0}", versionJson.Id);
  62. //download java
  63. if (Parameters.Parameter.DownloadJava)
  64. {
  65. try
  66. {
  67. await DownloadJava(httpClient, versionJson, cancellationToken);
  68. tasksIterator++;
  69. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  70. }
  71. catch (Exception ex) when (ex is (ArgumentException or HttpRequestException) )
  72. {
  73. _logger.Warning(ex.Message);
  74. }
  75. }
  76. await DownloadMainJar(httpClient, versionJson, cancellationToken);
  77. tasksIterator++;
  78. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  79. //end download jar
  80. //download libraries
  81. await DownloadLibraries(httpClient, versionJson, cancellationToken);
  82. tasksIterator++;
  83. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  84. await DownloadAssets(httpClient, versionJson, cancellationToken);
  85. Parameters.Action.SetProgress(100);
  86. tasksIterator++;
  87. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  88. if (Parameters.Parameter.InstallForge)
  89. {
  90. await DownloadForge(httpClient, versionJson, cancellationToken);
  91. }
  92. if (Parameters.Parameter.InstallOptifine)
  93. {
  94. await DownloadOptifine(httpClient, versionJson, cancellationToken);
  95. }
  96. if (Parameters.Parameter.InstallForgeOptifine)
  97. {
  98. await DownloadForgeOptifine(httpClient, versionJson, cancellationToken);
  99. }
  100. if (Parameters.Parameter.InstallForge || Parameters.Parameter.InstallOptifine || Parameters.Parameter.InstallForgeOptifine || Parameters.Parameter.InstallFabric)
  101. {
  102. tasksIterator++;
  103. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  104. }
  105. if (Parameters.Data.SelectedModpack is not null)
  106. {
  107. await DownloadModpack(httpClient, cancellationToken);
  108. tasksIterator++;
  109. Parameters.Action.TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
  110. }
  111. //RemoveProgressBar(webClient);
  112. //Progress = 100;
  113. _logger.Debug("Downloading finished.");
  114. Parameters.Action.DownloadingFileName("Finished!");
  115. Parameters.Action.IsControlsEnabled(true);
  116. //webClient.Dispose();
  117. }
  118. catch (TaskCanceledException ex)
  119. {
  120. Parameters.Action.IsControlsEnabled(true);
  121. if (Parameters.Data.SelectedModpack is not null)
  122. Parameters.Action.DownloadingFileName($"Downloading {Parameters.Data.SelectedModpack.Name} canceled.");
  123. else if (Parameters.Data.SelectedVersion is not null)
  124. Parameters.Action.DownloadingFileName($"Downloading {Parameters.Data.SelectedVersion.Id} canceled.");
  125. else
  126. Parameters.Action.DownloadingFileName("Downloading canceled: version config is null!");
  127. _logger.Warning(ex.Message);
  128. return;// Task.FromCanceled(cancellationToken);
  129. }
  130. catch (Exception ex)
  131. {
  132. Parameters.Action.IsControlsEnabled(true);
  133. if (Parameters.Data.SelectedModpack is not null)
  134. Parameters.Action.DownloadingFileName($"Error occured while downloading {Parameters.Data.SelectedModpack.Name}.");
  135. else if (Parameters.Data.SelectedVersion is not null)
  136. Parameters.Action.DownloadingFileName($"Error occured while downloading {Parameters.Data.SelectedVersion.Id}.");
  137. else
  138. Parameters.Action.DownloadingFileName($"Error occured while downloading: version config is null!");
  139. _logger.Error(ex.Message);
  140. if (ex.StackTrace is not null)
  141. _logger.Error(ex.StackTrace);
  142. return;// Task.FromException(ex);
  143. }
  144. return;
  145. }
  146. private async Task DownloadModpack(HttpClient httpClient, CancellationToken cancellationToken)
  147. {
  148. ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedModpack);
  149. string modpackUrl = string.Empty;
  150. if (!string.IsNullOrWhiteSpace(Parameters.Data.SelectedModpack.Url))
  151. modpackUrl = Parameters.Data.SelectedModpack.Url;
  152. else
  153. modpackUrl = $"https://files.veloe.link/launcher/modpacks/{Parameters.Data.SelectedModpack.Name}.zip";
  154. if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}"))
  155. {
  156. _logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}");
  157. Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}");
  158. }
  159. _logger.Debug("Downloading: {0}", $"{Parameters.Data.SelectedModpack.Name}.zip");
  160. Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedModpack.Name}.zip");
  161. await DownloadFileAsync(modpackUrl, Settings.minecraftForlderPath + $"versions/{Parameters.Data.SelectedModpack.Name}.zip", cancellationToken, httpClient);
  162. FileStream? stream = null;
  163. if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
  164. {
  165. foreach (var file in Directory.GetFiles($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
  166. {
  167. try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
  168. finally { stream?.Close(); }
  169. }
  170. }
  171. if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config"))
  172. foreach (var file in Directory.GetFiles($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
  173. {
  174. try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
  175. finally { stream?.Close(); }
  176. }
  177. stream?.Dispose();
  178. if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods"))
  179. Directory.Delete($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/mods", true);
  180. if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config"))
  181. Directory.Delete($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/config", true);
  182. _logger.Debug("Extracting: {0}", $"{Parameters.Data.SelectedModpack.Name}.zip");
  183. Parameters.Action.DownloadingFileName($"Unpacking {Parameters.Data.SelectedModpack.Name}.zip");
  184. ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}.zip", Settings.minecraftForlderPath + $"versions/{Parameters.Data.SelectedModpack.Name}", true);
  185. File.Delete($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}.zip");
  186. File.WriteAllText($"{Settings.minecraftForlderPath}versions/{Parameters.Data.SelectedModpack.Name}/revision.json", JsonSerializer.Serialize(Parameters.Data.SelectedModpack.Revision));
  187. }
  188. private async Task DownloadForgeOptifine(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  189. {
  190. if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/mods"))
  191. {
  192. _logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
  193. Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
  194. }
  195. _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
  196. Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.jar");
  197. await DownloadFileAsync($@"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/Optifine{versionJson.Id}.jar", Settings.minecraftForlderPath + "versions/Forge" + versionJson.Id + "/mods/" + "Optifine" + versionJson.Id + ".jar", cancellationToken, httpClient);
  198. }
  199. private async Task DownloadOptifine(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  200. {
  201. var optifinePath = $"Optifine{versionJson.Id}";
  202. var optifineUrl = $"https://files.veloe.link/launcher/optifine/Optifine{versionJson.Id}/";
  203. if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/{optifinePath}"))
  204. {
  205. _logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/" + optifinePath);
  206. Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/" + optifinePath);
  207. }
  208. _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.json");
  209. Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.json");
  210. await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.json", Settings.minecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".json", cancellationToken, httpClient);
  211. _logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
  212. Parameters.Action.DownloadingFileName($"Optifine{versionJson.Id}.json");
  213. await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.jar", Settings.minecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".jar", cancellationToken, httpClient);
  214. _logger.Debug("Downloading: {0}", "libraries.zip");
  215. Parameters.Action.DownloadingFileName("libraries.zip");
  216. await DownloadFileAsync(optifineUrl + "libraries.zip", Settings.minecraftForlderPath + "versions/" + optifinePath + "/libraries.zip", cancellationToken, httpClient);
  217. _logger.Debug("Extracting: {0}", "libraries.zip");
  218. Parameters.Action.DownloadingFileName("Unpacking libraries.zip");
  219. ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip", Settings.minecraftForlderPath, true);
  220. File.Delete($"{Settings.minecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip");
  221. }
  222. private async Task DownloadForge(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  223. {
  224. var forgePath = $"Forge{versionJson.Id}";
  225. var forgeUrl = $"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/";
  226. if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}"))
  227. {
  228. _logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/" + forgePath);
  229. Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/" + forgePath);
  230. }
  231. _logger.Debug("Downloading: {0}", $"Forge{versionJson.Id}.json");
  232. Parameters.Action.DownloadingFileName($"Forge{versionJson.Id}.json");
  233. await DownloadFileAsync($"{forgeUrl}Forge{versionJson.Id}.json", Settings.minecraftForlderPath + "versions/" + forgePath + "/" + forgePath + ".json", cancellationToken, httpClient);
  234. _logger.Debug("Downloading: {0}", "libraries.zip");
  235. Parameters.Action.DownloadingFileName("libraries.zip");
  236. await DownloadFileAsync(forgeUrl + "libraries.zip", Settings.minecraftForlderPath + "versions/" + forgePath + "/libraries.zip", cancellationToken, httpClient);
  237. _logger.Debug("Extracting: {0}", "libraries.zip");
  238. Parameters.Action.DownloadingFileName("Unpacking libraries.zip");
  239. ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip", Settings.minecraftForlderPath, true);
  240. File.Delete($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip");
  241. }
  242. private async Task DownloadAssets(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  243. {
  244. var assetsJson = await Downloader.DownloadAndDeserializeJsonData<AssetsManifest>(versionJson.AssetIndex.Url);
  245. var assetsPath = $"{Settings.minecraftForlderPath}{(versionJson.Assets == "pre-1.6" ? "resources" : $"assets/{versionJson.Assets}/objects")}";
  246. var assetsUrl = "https://resources.download.minecraft.net/";
  247. //download assets json
  248. _logger.Debug("Downloading: {0}", versionJson.Assets + ".json");
  249. if (!Directory.Exists(Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/"))
  250. Directory.CreateDirectory(Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/");
  251. await DownloadFileAsync(versionJson.AssetIndex.Url, Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/" + versionJson.Assets + ".json", cancellationToken);
  252. _logger.Debug("Downloading assets.");
  253. Parameters.Action.SetProgress(0);
  254. var assetsCount = assetsJson?.Objects?.Count ?? 1;
  255. var assetsIterator = 1;
  256. List<Task> assetsDownloadTasks = new(assetsJson?.Objects?.Count ?? 1024);
  257. using SemaphoreSlim concurrencySemaphore = new(40, 40);
  258. foreach (var asset in assetsJson?.Objects?.DistinctBy(a => a.Value.Hash) ?? new Dictionary<string, Asset>())
  259. {
  260. assetsDownloadTasks.Add(Task.Run(async () =>
  261. {
  262. var isPreVersion = versionJson.Assets == "pre-1.6";
  263. var folder = isPreVersion ?
  264. Path.GetDirectoryName(asset.Key) :
  265. asset.Value.Hash[..2];
  266. var name = isPreVersion ?
  267. Path.GetFileName(asset.Key) :
  268. asset.Value.Hash;
  269. var assetRoot = folder.Contains("icons") ? $"{Settings.minecraftForlderPath}assets/{versionJson.Assets}" : assetsPath;
  270. var chksum = string.Empty;
  271. //here hash check
  272. if (File.Exists(Path.Combine(assetRoot, folder, name)))
  273. {
  274. Parameters.Action.DownloadingFileName($"Checking hash {assetsIterator} of {assetsCount}");
  275. using FileStream fop = File.OpenRead(Path.Combine(assetRoot, folder, name));
  276. byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
  277. chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
  278. fop.Close();
  279. }
  280. if (chksum != (isPreVersion ? asset.Value.Hash : name))
  281. {
  282. await concurrencySemaphore.WaitAsync(cancellationToken);
  283. try
  284. {
  285. if (!Directory.Exists(assetRoot + "/" + folder))
  286. Directory.CreateDirectory(assetRoot + "/" + folder);
  287. _logger.Debug("Downloading asset: {0} ", name);
  288. //DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");
  289. Parameters.Action.DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");
  290. await DownloadFileAsync(assetsUrl + asset.Value.Hash[..2] + "/" + asset.Value.Hash, Path.Combine(assetRoot, folder , name), cancellationToken, httpClient);
  291. }
  292. finally
  293. {
  294. concurrencySemaphore.Release();
  295. }
  296. }
  297. Parameters.Action.SetProgress(assetsIterator * 100 / assetsCount);
  298. Interlocked.Increment(ref assetsIterator);
  299. }));
  300. }
  301. Task.WaitAll(assetsDownloadTasks.ToArray());
  302. }
  303. private async Task DownloadLibraries(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  304. {
  305. _logger.Debug("Downloading libraries.");
  306. foreach (var library in versionJson.Libraries)
  307. {
  308. var libPath = Settings.minecraftForlderPath + "libraries/";
  309. string libUrl = string.Empty;
  310. string sha1 = string.Empty;
  311. // getting path and url if universal lib
  312. if (library.Natives is null)
  313. {
  314. libPath += library.Downloads.Artifact.Path;
  315. libUrl = library.Downloads.Artifact.Url;
  316. sha1 = library.Downloads.Artifact.Sha1;
  317. }
  318. else
  319. { // getting path if native
  320. libUrl = "";
  321. if (library.Downloads.Classifiers is not null)
  322. {
  323. if (OperatingSystem.IsWindows() && library.Natives.Windows is not null)
  324. {
  325. if (library.Downloads.Classifiers.NativesWindows is not null)
  326. {
  327. libPath += library.Downloads.Classifiers.NativesWindows.Path;
  328. libUrl = library.Downloads.Classifiers.NativesWindows.Url;
  329. sha1 = library.Downloads.Classifiers.NativesWindows.Sha1;
  330. }
  331. else
  332. {
  333. if (Environment.Is64BitOperatingSystem && library.Downloads.Classifiers.NativesWindows64 is not null)
  334. {
  335. libPath += library.Downloads.Classifiers.NativesWindows64.Path;
  336. libUrl = library.Downloads.Classifiers.NativesWindows64.Url;
  337. sha1 = library.Downloads.Classifiers.NativesWindows64.Sha1;
  338. }
  339. else if (library.Downloads.Classifiers.NativesWindows32 is not null)
  340. {
  341. libPath += library.Downloads.Classifiers.NativesWindows32.Path;
  342. libUrl = library.Downloads.Classifiers.NativesWindows32.Url;
  343. sha1 = library.Downloads.Classifiers.NativesWindows32.Sha1;
  344. }
  345. }
  346. }
  347. if (OperatingSystem.IsLinux() && library.Natives.Linux is not null && library.Downloads.Classifiers.NativesLinux is not null)
  348. {
  349. libPath += library.Downloads.Classifiers.NativesLinux.Path;
  350. libUrl = library.Downloads.Classifiers.NativesLinux.Url;
  351. sha1 = library.Downloads.Classifiers.NativesLinux.Url;
  352. }
  353. }
  354. }
  355. //if no lib url
  356. if (libUrl == string.Empty)
  357. continue;
  358. var libName = Path.GetFileName(libPath);
  359. var libDir = Path.GetDirectoryName(libPath);
  360. if (libName is null || libDir is null)
  361. {
  362. if (libPath == string.Empty)
  363. _logger.Warning("Library dir, name are null, because libraryPath is empty! Skipping...");
  364. else
  365. _logger.Warning("Library dir or name are null! Skipping...");
  366. continue;
  367. }
  368. //checking rules
  369. if (library.Rules == null)
  370. {
  371. if (!Directory.Exists(libDir))
  372. {
  373. _logger.Debug("Creating path: {0}", libDir);
  374. Directory.CreateDirectory(libDir);
  375. }
  376. var chksum = string.Empty;
  377. if (File.Exists(libPath))
  378. {
  379. Parameters.Action.DownloadingFileName($"Checking hash: {libName}");
  380. FileStream fop = File.OpenRead(libPath);
  381. byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
  382. chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
  383. fop.Close();
  384. fop.Dispose();
  385. }
  386. if (chksum != sha1)
  387. {
  388. _logger.Debug("Downloading: {0}", libName);
  389. Parameters.Action.DownloadingFileName(libName);
  390. await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);
  391. }
  392. }
  393. else
  394. {
  395. foreach (var rule in library.Rules)
  396. {
  397. if (rule.Action == "allow") // && rule.os is null
  398. {
  399. if (!Directory.Exists(libDir))
  400. {
  401. _logger.Debug("Creating path: {0}", libDir);
  402. Directory.CreateDirectory(libDir);
  403. }
  404. var chksum = string.Empty;
  405. if (File.Exists(libDir + "/" + libName) && sha1 != string.Empty)
  406. {
  407. Parameters.Action.DownloadingFileName($"Checking hash: {libName}");
  408. FileStream fop = File.OpenRead(libDir + "/" + libName);
  409. byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
  410. chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
  411. fop.Close();
  412. fop.Dispose();
  413. }
  414. if (chksum != sha1)
  415. {
  416. _logger.Debug("Downloading: {0}", libName);
  417. Parameters.Action.DownloadingFileName(libName);
  418. await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);
  419. }
  420. continue;
  421. }
  422. //fabic on start needs all libraries for all os, so os check removed
  423. /*
  424. if (rule.action == "allow" && (rule.os.name == "windows" || rule.os.name == "linux" || rule.os.name == "osx" ))
  425. {
  426. if (!Directory.Exists(libPath))
  427. {
  428. _logger.Debug("Creating path: {0}", path);
  429. Directory.CreateDirectory(libPath);
  430. }
  431. _logger.Debug("Downloading: {0}", libName);
  432. DownloadingFileName = libName;
  433. webClient.DownloadFileAsync(new System.Uri(libUrl), libPath + "/" + libName);
  434. }
  435. */
  436. }
  437. }
  438. //unpacking native libs
  439. if ((library.Natives is not null || library.Downloads.Classifiers is not null) && File.Exists(libPath)/*&& chksum != sha1*/)
  440. {
  441. try
  442. {
  443. if (!(library.Downloads.Classifiers is not null && (
  444. library.Downloads.Classifiers.NativesWindows is not null ||
  445. library.Downloads.Classifiers.NativesWindows64 is not null ||
  446. (library.Downloads.Classifiers.NativesWindows32 is not null && OperatingSystem.IsWindows()) ||
  447. (library.Downloads.Classifiers.NativesLinux is not null && OperatingSystem.IsLinux()))))
  448. continue;
  449. if (!Directory.Exists(Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/"))
  450. {
  451. _logger.Debug("Creating path: {0}", Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
  452. Directory.CreateDirectory(Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
  453. }
  454. _logger.Debug("Extracting {0} to {1}", libName, Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
  455. ZipFile.ExtractToDirectory(libDir + "/" + libName, Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/", true);
  456. }
  457. catch (IOException)
  458. {
  459. _logger.Error("IOException on native lib extraction");
  460. }
  461. //TODO delete META-INF and sha1 files after
  462. }
  463. }
  464. }
  465. private async Task DownloadMainJar(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  466. {
  467. //download json
  468. var path = Settings.minecraftForlderPath + "/versions/" + versionJson.Id;
  469. //download jar
  470. if (!Directory.Exists(path))
  471. {
  472. _logger.Debug("Creating path: {0}", path);
  473. Directory.CreateDirectory(path);
  474. }
  475. string chksum = string.Empty;
  476. //here hash check
  477. if (File.Exists(path + "/" + versionJson.Id + ".jar"))
  478. {
  479. Parameters.Action.DownloadingFileName($"Checking hash: {versionJson.Id}.jar");
  480. FileStream fop = File.OpenRead(path + "/" + versionJson.Id + ".jar");
  481. byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
  482. chksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
  483. fop.Close();
  484. fop.Dispose();
  485. }
  486. if (chksum != versionJson.Downloads.Client.Sha1)
  487. {
  488. _logger.Debug("Downloading {0}", $"{versionJson.Id}.jar");
  489. Parameters.Action.DownloadingFileName($"{versionJson.Id}.jar");
  490. await DownloadFileAsync(versionJson.Downloads.Client.Url, path + "/" + versionJson.Id + ".jar", cancellationToken, httpClient);
  491. }
  492. }
  493. private async Task DownloadJava(HttpClient httpClient, Entity.Version.Version versionJson, CancellationToken cancellationToken)
  494. {
  495. _logger.Debug("Getting required Java version.");
  496. var javaVersion = (versionJson.JavaVersion?.MajorVersion ??
  497. (versionJson.Assets == "legacy" ? 8 :
  498. throw new ArgumentException("Required Java version was not found in json file.")));
  499. var javaUrl = $"https://files.veloe.link/launcher/java/{javaVersion}/{(OperatingSystem.IsWindows() ? "windows" : "linux")}{(Environment.Is64BitOperatingSystem ? "64" : "32")}/java.zip";
  500. if (await IsFileAvaliable(javaUrl, cancellationToken))
  501. {
  502. _logger.Debug("Downloading Java");
  503. Parameters.Action.DownloadingFileName("java.zip");
  504. await DownloadFileAsync(javaUrl, Settings.minecraftForlderPath + "java.zip", cancellationToken, httpClient);
  505. _logger.Debug("Unpacking Java");
  506. Parameters.Action.DownloadingFileName("Unpacking java.zip");
  507. ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}java.zip", Settings.minecraftForlderPath, true);
  508. File.Delete($"{Settings.minecraftForlderPath}java.zip");
  509. }
  510. else
  511. throw new HttpRequestException("Required Java version was not found on server.");
  512. }
  513. private async Task<Entity.Version.Version?> GetVersionJson()
  514. {
  515. if (Parameters.Data.SelectedModpack is not null)
  516. {
  517. ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedVersion);
  518. ArgumentNullException.ThrowIfNull(Parameters.Data.SelectedModpack);
  519. if (Parameters.Data.SelectedVersion.Id != Parameters.Data.SelectedModpack.Version)
  520. throw new ArgumentException("Selected version id not equal selected modpack version!");
  521. Parameters.Parameter.InstallForge = Parameters.Data.SelectedModpack.Forge;
  522. Parameters.Parameter.InstallForgeOptifine = Parameters.Data.SelectedModpack.ForgeOptifine;
  523. Parameters.Parameter.InstallOptifine = Parameters.Data.SelectedModpack.Optifine;
  524. Parameters.Parameter.InstallFabric = Parameters.Data.SelectedModpack.Fabric;
  525. _logger.Debug("Downloading {0}.json", Parameters.Data.SelectedVersion.Id);
  526. Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedVersion.Id}.json");
  527. return await Downloader.DownloadAndDeserializeJsonData<Entity.Version.Version>(Parameters.Data.SelectedVersion.Url, Settings.minecraftForlderPath + "versions/" + Parameters.Data.SelectedVersion.Id + "/", Parameters.Data.SelectedVersion.Id + ".json");
  528. }
  529. else
  530. {
  531. Parameters.Action.DownloadingFileName($"{Parameters.Data.SelectedVersion.Id}.json");
  532. _logger.Debug("Downloading {0}.json", Parameters.Data.SelectedVersion.Id);
  533. return await Downloader.DownloadAndDeserializeJsonData<Entity.Version.Version>(Parameters.Data.SelectedVersion.Url, Settings.minecraftForlderPath + "versions/" + Parameters.Data.SelectedVersion.Id + "/", Parameters.Data.SelectedVersion.Id + ".json");
  534. }
  535. }
  536. public static async Task<T?> DownloadAndDeserializeJsonData<T>(string url, string path = "", string filename = "", Serilog.ILogger? logger = null) where T : new()
  537. {
  538. string jsonData;
  539. if (logger is null) logger = Settings.logger;
  540. try
  541. {
  542. logger.Debug($"Downloading: {url.Split('/').Last()}");
  543. jsonData = await _sHttpClient.GetStringAsync(url);
  544. }
  545. catch (Exception ex)
  546. {
  547. throw new WebException($"An error occured while downloading {url}. Check your internet connection.",ex);
  548. }
  549. if (string.IsNullOrEmpty(jsonData))
  550. {
  551. logger.Warning("Empty string!");
  552. return new T();
  553. }
  554. else
  555. {
  556. if (!string.IsNullOrEmpty(filename) && !string.IsNullOrEmpty(path))
  557. {
  558. if (!Directory.Exists(path))
  559. {
  560. logger.Debug("Creating path: {0}", path);
  561. Directory.CreateDirectory(path);
  562. }
  563. File.WriteAllText(path + filename, jsonData);
  564. }
  565. //_logger.Debug("Return serialized string.");
  566. return JsonSerializer.Deserialize<T>(jsonData, new JsonSerializerOptions{PropertyNameCaseInsensitive = true});
  567. }
  568. }
  569. public static async Task DownloadFileAsync(string url, string path, CancellationToken ct, HttpClient? client = null)
  570. {
  571. client ??= _sHttpClient;
  572. using HttpResponseMessage response = await client.GetAsync(new System.Uri(url),HttpCompletionOption.ResponseHeadersRead, ct);
  573. response.EnsureSuccessStatusCode();
  574. using Stream contentStream = await response.Content.ReadAsStreamAsync(ct);
  575. using FileStream fileStream = File.OpenWrite(path);
  576. await contentStream.CopyToAsync(fileStream,ct);
  577. }
  578. public static async Task<bool> IsFileAvaliable(string url, CancellationToken ct)
  579. {
  580. using HttpResponseMessage response = await _sHttpClient.GetAsync(new System.Uri(url), HttpCompletionOption.ResponseHeadersRead, ct);
  581. return response.IsSuccessStatusCode;
  582. }
  583. public async Task DownloadFabricLibraries(CancellationToken ct)
  584. {
  585. var handler = new HttpClientHandler() { AllowAutoRedirect = true };
  586. var ph = new ProgressMessageHandler(handler);
  587. ph.HttpSendProgress += (_, args) =>
  588. {
  589. Parameters.Action.SetProgress(args.ProgressPercentage);
  590. };
  591. ph.HttpReceiveProgress += (_, args) =>
  592. {
  593. Parameters.Action.SetProgress(args.ProgressPercentage);
  594. };
  595. using var httpClient = new HttpClient(ph);
  596. foreach (var library in Parameters.Data.VersionJson.Libraries)
  597. {
  598. if (string.IsNullOrEmpty(library.Name) || string.IsNullOrEmpty(library.Url))
  599. continue;
  600. var relativePath = StartCommandBuilder.GetLibRelativePathFromName(library.Name);
  601. var libPath = Settings.minecraftForlderPath + "libraries/" + relativePath;
  602. var dirPath = Path.GetDirectoryName(libPath);
  603. if (!Directory.Exists(dirPath))
  604. {
  605. Directory.CreateDirectory(dirPath);
  606. }
  607. // some links may be unawailable
  608. if (await IsFileAvaliable($"{library.Url}{relativePath}", ct))
  609. await DownloadFileAsync($"{library.Url}{relativePath}", libPath, ct, httpClient);
  610. else
  611. _logger.Warning($"Library {library.Name} url is not accessible. Skipping...");
  612. }
  613. }
  614. }