diff --git a/src/BlitzKit.CLI/Functions/Unpacker.cs b/src/BlitzKit.CLI/Functions/Unpacker.cs index 7875f568d..9b656bd3a 100644 --- a/src/BlitzKit.CLI/Functions/Unpacker.cs +++ b/src/BlitzKit.CLI/Functions/Unpacker.cs @@ -10,6 +10,11 @@ using CUE4Parse.UE4.Pak.Objects; using CUE4Parse.UE4.Readers; using CUE4Parse.UE4.Versions; using DotNet.Globbing; +using System.Net.Http; +using System.Threading; +using System.IO; +using System.Threading.Tasks; +using System.Collections.Generic; namespace BlitzKit.CLI.Functions { @@ -17,12 +22,10 @@ namespace BlitzKit.CLI.Functions { public const string CONTENT_GROUP = "p14y73c7_p5e_b10gg3RS_big_play"; private const string CONTAINERS_PATH = "../../temp/containers/"; - - // this may be different from the one provided in args so check this and not args[1] - // we don't wanna delete my local installation of the game when debugging haha! private const string TEMP_DEPOT_DIR = "../../temp/depot/"; public const string WG_DLC_DOMAIN = "http://dl-wotblitz-gc.wargaming.net"; private const string BUILD_MANIFEST_OS = "Windows"; + private static readonly SemaphoreSlim semaphore = new(4); // Limit to 4 concurrent downloads public static async Task Unpack(string[] args) { @@ -58,25 +61,20 @@ namespace BlitzKit.CLI.Functions PrettyLog.Log($"Downloading {manifest.PakFiles.Count} containers..."); - int done = 0; + Directory.CreateDirectory(CONTAINERS_PATH); + List downloadTasks = new(); + foreach (var pakFile in manifest.PakFiles) { var localContainerPath = Path.Combine(CONTAINERS_PATH, pakFile.FileName); var fullContainerURL = $"{WG_DLC_DOMAIN}/dlc/{CONTENT_GROUP}/dlc/{pakFile.RelativeUrl}"; - var response = await client.GetAsync(fullContainerURL); - var bytes = await response.Content.ReadAsByteArrayAsync(); - - Directory.CreateDirectory(CONTAINERS_PATH); - File.WriteAllBytes(localContainerPath, bytes); - done++; - - PrettyLog.Log($"Downloaded {done}/{manifest.PakFiles.Count} containers..."); + downloadTasks.Add(DownloadFileAsync(client, fullContainerURL, localContainerPath, manifest.PakFiles.Count)); } + await Task.WhenAll(downloadTasks); + PrettyLog.Log("Moving pre-bundled containers..."); - foreach ( - var file in Directory.GetFiles(Path.Combine(TEMP_DEPOT_DIR, "Blitz/Content/Paks"), "*.pak") - ) + foreach (var file in Directory.GetFiles(Path.Combine(TEMP_DEPOT_DIR, "Blitz/Content/Paks"), "*.pak")) { File.Move(file, Path.Combine(CONTAINERS_PATH, Path.GetFileName(file))); } @@ -85,19 +83,39 @@ namespace BlitzKit.CLI.Functions Directory.Delete(TEMP_DEPOT_DIR, true); } + private static async Task DownloadFileAsync(HttpClient client, string url, string localPath, int totalFiles) + { + await semaphore.WaitAsync(); + try + { + using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + using Stream contentStream = await response.Content.ReadAsStreamAsync(), + fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); + + await contentStream.CopyToAsync(fileStream); + PrettyLog.Log($"Downloaded {Path.GetFileName(localPath)} ({totalFiles} total)"); + } + catch (Exception ex) + { + PrettyLog.Log($"Failed to download {url}: {ex.Message}"); + } + finally + { + semaphore.Release(); + } + } + public static async Task FetchManifest(string group, string build) { PrettyLog.Log($"Fetching manifest for group {group} and build {build}"); - string url = - $"{WG_DLC_DOMAIN}/dlc/{group}/dlc/BuildManifest-{BUILD_MANIFEST_OS}-{build}.json"; + string url = $"{WG_DLC_DOMAIN}/dlc/{group}/dlc/BuildManifest-{BUILD_MANIFEST_OS}-{build}.json"; using HttpClient client = new(); string jsonString = await client.GetStringAsync(url); - DlcManifest manifest = - JsonSerializer.Deserialize(jsonString) - ?? throw new Exception("Failed to deserialize manifest"); - - return manifest; + return JsonSerializer.Deserialize(jsonString) + ?? throw new Exception("Failed to deserialize manifest"); } } }