Downloader.cs 35 KB

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