From 3e55bccd56e6485b5e7842493fe445a808267d10 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Thu, 22 Aug 2024 23:54:36 +0700 Subject: [PATCH 01/25] Moving Legacy ``Http()`` to ``DownloadClient()`` (phase 1) Moving HSR dispatch and gateway routines to the new DownloadClient instance --- .../Classes/CachesManagement/Honkai/Fetch.cs | 24 +++-- .../Classes/CachesManagement/Honkai/Update.cs | 2 +- .../CachesManagement/StarRail/Fetch.cs | 95 ++++++++++--------- .../CachesManagement/StarRail/Update.cs | 2 +- .../BaseClass/GameInstallPackage.cs | 2 +- .../InstallManagerBase.PkgVersion.cs | 2 +- .../BaseClass/InstallManagerBase.cs | 2 +- .../GameConversionManagement.cs | 2 +- .../Classes/Interfaces/Class/ProgressBase.cs | 33 +++++++ .../RegionManagement/FallbackCDNUtil.cs | 2 +- .../Classes/RepairManagement/Genshin/Fetch.cs | 2 +- .../RepairManagement/Genshin/Repair.cs | 2 +- .../Classes/RepairManagement/Honkai/Fetch.cs | 43 +++++---- .../Classes/RepairManagement/Honkai/Repair.cs | 2 +- .../RepairManagement/StarRail/Fetch.cs | 18 ++-- .../RepairManagement/StarRail/Repair.cs | 2 +- CollapseLauncher/Program.cs | 2 +- .../Pages/Dialogs/InstallationConvert.xaml.cs | 2 +- .../XAMLs/Updater/Classes/Updater.cs | 2 +- .../XAMLs/Updater/UpdaterWindow.xaml.cs | 2 +- .../GenshinDispatchHelper.cs | 4 +- Hi3Helper.EncTool | 2 +- Hi3Helper.Http | 2 +- 23 files changed, 148 insertions(+), 103 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index 9853de4be..0d9bb963e 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -5,6 +5,7 @@ using Hi3Helper.EncTool; using Hi3Helper.EncTool.Parser.KianaDispatch; using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.UABT; using System; using System.Collections.Generic; @@ -34,6 +35,9 @@ private async Task> Fetch(CancellationToken token) .SetAllowedDecompression(DecompressionMethods.None) .Create(); + // Use a new DownloadClient for fetching + DownloadClient downloadClient = DownloadClient.CreateInstance(httpClientNew); + // Use HttpClient instance on fetching using Http httpClient = new Http(true, 5, 1000, _userAgent, httpClientNew); try @@ -42,7 +46,7 @@ private async Task> Fetch(CancellationToken token) httpClient.DownloadProgress += _httpClient_FetchAssetProgress; // Build _gameRepoURL from loading Dispatcher and Gateway - await BuildGameRepoURL(token); + await BuildGameRepoURL(downloadClient, token); // Iterate type and do fetch foreach (CacheAssetType type in Enum.GetValues()) @@ -62,7 +66,7 @@ private async Task> Fetch(CancellationToken token) // uint = Count of the assets available // long = Total size of the assets available - (int, long) count = await FetchByType(type, httpClient, returnAsset, token); + (int, long) count = await FetchByType(type, downloadClient, returnAsset, token); // Write a log about the metadata LogWriteLine($"Cache Metadata [T: {type}]:", LogType.Default, true); @@ -84,7 +88,7 @@ private async Task> Fetch(CancellationToken token) return returnAsset; } - private async Task BuildGameRepoURL(CancellationToken token) + private async Task BuildGameRepoURL(DownloadClient downloadClient, CancellationToken token) { KianaDispatch dispatch = null; Exception lastException = null; @@ -102,7 +106,7 @@ private async Task BuildGameRepoURL(CancellationToken token) string key = _gameVersionManager.GamePreset.DispatcherKey; // Try assign dispatcher - dispatch = await KianaDispatch.GetDispatch(baseURL, + dispatch = await KianaDispatch.GetDispatch(downloadClient, baseURL, _gameVersionManager.GamePreset.GameDispatchURLTemplate, _gameVersionManager.GamePreset.GameDispatchChannelName, key, _gameVersion.VersionArray, token); @@ -120,13 +124,13 @@ private async Task BuildGameRepoURL(CancellationToken token) // Get gatewayURl and fetch the gateway _gameGateway = - await KianaDispatch.GetGameserver(dispatch!, _gameVersionManager.GamePreset.GameGatewayDefault!, token); + await KianaDispatch.GetGameserver(downloadClient, dispatch!, _gameVersionManager.GamePreset.GameGatewayDefault!, token); _gameRepoURL = BuildAssetBundleURL(_gameGateway); } private string BuildAssetBundleURL(KianaDispatch gateway) => CombineURLFromString(gateway!.AssetBundleUrls![0], "/{0}/editor_compressed/"); - private async Task<(int, long)> FetchByType(CacheAssetType type, Http httpClient, List assetIndex, CancellationToken token) + private async Task<(int, long)> FetchByType(CacheAssetType type, DownloadClient downloadClient, List assetIndex, CancellationToken token) { // Set total activity string as "Fetching Caches Type: " _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, type); @@ -145,7 +149,7 @@ private async Task BuildGameRepoURL(CancellationToken token) // Get a direct HTTP Stream await using HttpResponseInputStream remoteStream = await HttpResponseInputStream.CreateStreamAsync( - httpClient.GetHttpClient(), assetIndexURL, null, null, token); + downloadClient.GetHttpClient(), assetIndexURL, null, null, token); using XORStream stream = new XORStream(remoteStream); @@ -339,16 +343,16 @@ private bool IsValidRegionFile(string input, string lang) public KianaDispatch GetCurrentGateway() => _gameGateway; public async Task<(List, string, string, int)> GetCacheAssetList( - Http httpClient, CacheAssetType type, CancellationToken token) + DownloadClient downloadClient, CacheAssetType type, CancellationToken token) { // Initialize asset index for the return List returnAsset = new(); // Build _gameRepoURL from loading Dispatcher and Gateway - await BuildGameRepoURL(token); + await BuildGameRepoURL(downloadClient, token); // Fetch the progress - _ = await FetchByType(type, httpClient, returnAsset, token); + _ = await FetchByType(type, downloadClient, returnAsset, token); // Return the list and base asset bundle repo URL return (returnAsset, _gameGateway!.ExternalAssetUrls!.FirstOrDefault(), BuildAssetBundleURL(_gameGateway), diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index eecf789db..900297c28 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -2,7 +2,7 @@ using CollapseLauncher.Interfaces; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs index ec9f5e646..bb612bcd9 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs @@ -1,8 +1,12 @@ -using Hi3Helper; +using CollapseLauncher.Helper; +using Hi3Helper; using Hi3Helper.EncTool.Parser.AssetMetadata.SRMetadataAsset; +using Hi3Helper.Http; using System; using System.Collections.Generic; using System.IO; +using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,59 +24,58 @@ private async Task> Fetch(CancellationToken token) // Initialize asset index for the return List returnAsset = new List(); - try - { - // Subscribe the event listener - _innerGameVersionManager!.StarRailMetadataTool!.HttpEvent += _httpClient_FetchAssetProgress; + // Initialize new proxy-aware HttpClient + using HttpClient client = new HttpClientBuilder() + .UseLauncherConfig(_downloadThreadCount + 16) + .SetUserAgent(_userAgent) + .SetAllowedDecompression(DecompressionMethods.None) + .Create(); + + // Initialize the new DownloadClient + DownloadClient downloadClient = DownloadClient.CreateInstance(client); - // Initialize metadata - // Set total activity string as "Fetching Caches Type: Dispatcher" - _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, CacheAssetType.Dispatcher); - _status!.IsProgressAllIndetermined = true; - _status!.IsIncludePerFileIndicator = false; - UpdateStatus(); + // Initialize metadata + // Set total activity string as "Fetching Caches Type: Dispatcher" + _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, CacheAssetType.Dispatcher); + _status!.IsProgressAllIndetermined = true; + _status!.IsIncludePerFileIndicator = false; + UpdateStatus(); - if (!await _innerGameVersionManager!.StarRailMetadataTool.Initialize(token, GetExistingGameRegionID(), Path.Combine(_gamePath!, $"{Path.GetFileNameWithoutExtension(_gameVersionManager!.GamePreset!.GameExecutableName)}_Data\\Persistent"))) - throw new InvalidDataException("The dispatcher response is invalid! Please open an issue to our GitHub page to report this issue."); + if (!await _innerGameVersionManager!.StarRailMetadataTool.Initialize(token, downloadClient, _httpClient_FetchAssetProgress, GetExistingGameRegionID(), Path.Combine(_gamePath!, $"{Path.GetFileNameWithoutExtension(_gameVersionManager!.GamePreset!.GameExecutableName)}_Data\\Persistent"))) + throw new InvalidDataException("The dispatcher response is invalid! Please open an issue to our GitHub page to report this issue."); - // Iterate type and do fetch - foreach (SRAssetType type in Enum.GetValues()) + // Iterate type and do fetch + foreach (SRAssetType type in Enum.GetValues()) + { + // Skip for unused type + switch (type) { - // Skip for unused type - switch (type) - { - case SRAssetType.Audio: - case SRAssetType.Video: - case SRAssetType.Block: - case SRAssetType.Asb: - continue; - } - - // uint = Count of the assets available - // long = Total size of the assets available - (int, long) count = await FetchByType(type, returnAsset, token); - - // Write a log about the metadata - LogWriteLine($"Cache Metadata [T: {type}]:", LogType.Default, true); - LogWriteLine($" Cache Count = {count.Item1}", LogType.NoTag, true); - LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); - - // Increment the Total Size and Count - _progressAllCountTotal += count.Item1; - _progressAllSizeTotal += count.Item2; + case SRAssetType.Audio: + case SRAssetType.Video: + case SRAssetType.Block: + case SRAssetType.Asb: + continue; } - } - finally - { - // Unsubscribe the event listener and dispose Http client - _innerGameVersionManager!.StarRailMetadataTool!.HttpEvent -= _httpClient_FetchAssetProgress; + + // uint = Count of the assets available + // long = Total size of the assets available + (int, long) count = await FetchByType(downloadClient, _httpClient_FetchAssetProgress, type, returnAsset, token); + + // Write a log about the metadata + LogWriteLine($"Cache Metadata [T: {type}]:", LogType.Default, true); + LogWriteLine($" Cache Count = {count.Item1}", LogType.NoTag, true); + LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); + + // Increment the Total Size and Count + _progressAllCountTotal += count.Item1; + _progressAllSizeTotal += count.Item2; } // Return asset index return returnAsset; } - private async Task<(int, long)> FetchByType(SRAssetType type, List assetIndex, CancellationToken token) + private async Task<(int, long)> FetchByType(DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, SRAssetType type, List assetIndex, CancellationToken token) { // Set total activity string as "Fetching Caches Type: " _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, type); @@ -85,17 +88,17 @@ private async Task> Fetch(CancellationToken token) switch (type) { case SRAssetType.IFix: - await _innerGameVersionManager!.StarRailMetadataTool!.ReadIFixMetadataInformation(token); + await _innerGameVersionManager!.StarRailMetadataTool!.ReadIFixMetadataInformation(downloadClient, downloadProgress, token); assetProperty = _innerGameVersionManager!.StarRailMetadataTool!.MetadataIFix!.GetAssets(); assetIndex!.AddRange(assetProperty!.AssetList!); return (assetProperty.AssetList.Count, assetProperty.AssetTotalSize); case SRAssetType.DesignData: - await _innerGameVersionManager!.StarRailMetadataTool!.ReadDesignMetadataInformation(token); + await _innerGameVersionManager!.StarRailMetadataTool!.ReadDesignMetadataInformation(downloadClient, downloadProgress, token); assetProperty = _innerGameVersionManager.StarRailMetadataTool.MetadataDesign!.GetAssets(); assetIndex!.AddRange(assetProperty!.AssetList!); return (assetProperty.AssetList.Count, assetProperty.AssetTotalSize); case SRAssetType.Lua: - await _innerGameVersionManager!.StarRailMetadataTool!.ReadLuaMetadataInformation(token); + await _innerGameVersionManager!.StarRailMetadataTool!.ReadLuaMetadataInformation(downloadClient, downloadProgress, token); assetProperty = _innerGameVersionManager.StarRailMetadataTool.MetadataLua!.GetAssets(); assetIndex!.AddRange(assetProperty!.AssetList!); return (assetProperty.AssetList.Count, assetProperty.AssetTotalSize); diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index d820b61ed..3b0e1219c 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -2,7 +2,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetMetadata.SRMetadataAsset; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs index c739c695c..08556982f 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs @@ -1,7 +1,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Preset; using System; using System.Collections.Generic; diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 1fdc7253d..715a9c23b 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -4,7 +4,7 @@ using CollapseLauncher.Pages; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Animation; diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 014cf8bba..1e7862352 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -23,7 +23,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetIndex; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Shared.Region; using Hi3Helper.Sophon; diff --git a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs index 2661ef168..993b773ab 100644 --- a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs +++ b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Metadata; using Hi3Helper; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Preset; using Hi3Helper.Shared.ClassStruct; using SharpHDiffPatch.Core; diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 753070ae7..e54219cd5 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -4,6 +4,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Preset; using Hi3Helper.Shared.Region; using Hi3Helper.Sophon; @@ -80,6 +81,38 @@ private void Init() protected void _innerObject_ProgressAdapter(object sender, TotalPerfileProgress e) => ProgressChanged?.Invoke(sender, e); protected void _innerObject_StatusAdapter(object sender, TotalPerfileStatus e) => StatusChanged?.Invoke(sender, e); + protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadProgress downloadData) + { + if (await CheckIfNeedRefreshStopwatch()) + { + double speed = downloadData.BytesDownloaded / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + TimeSpan timeLeftSpan = ((downloadData.BytesTotal - downloadData.BytesDownloaded) / speed).ToTimeSpanNormalized(); + double percentage = ConverterTool.GetPercentageNumber(downloadData.BytesDownloaded, downloadData.BytesTotal, 2); + + lock (_status!) + { + // Update fetch status + _status.IsProgressPerFileIndetermined = false; + _status.IsProgressAllIndetermined = false; + _status.ActivityPerFile = string.Format(Lang!._GameRepairPage!.PerProgressSubtitle3!, ConverterTool.SummarizeSizeSimple(speed)); + } + + lock (_progress!) + { + // Update fetch progress + _progress.ProgressPerFilePercentage = percentage; + _progress.ProgressAllSizeCurrent = downloadData.BytesDownloaded; + _progress.ProgressAllSizeTotal = downloadData.BytesTotal; + _progress.ProgressAllSpeed = speed; + _progress.ProgressAllTimeLeft = timeLeftSpan; + } + + // Push status and progress update + UpdateStatus(); + UpdateProgress(); + } + } + protected virtual void _httpClient_FetchAssetProgress(object sender, DownloadEvent e) { lock (_status!) diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index 20f7de797..91ac230e2 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.Region; using Squirrel.Sources; using System; diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index caf7379b4..3a38f5c6b 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -4,7 +4,7 @@ using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.EncTool.Parser.AssetIndex; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using System; using System.Collections.Generic; diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs index 3d7133d99..ddc9f3d6d 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs @@ -2,7 +2,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetIndex; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index b832e590a..74e49e1af 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -9,6 +9,7 @@ using Hi3Helper.EncTool.Parser.Cache; using Hi3Helper.EncTool.Parser.Senadina; using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.Win32; using System; @@ -69,6 +70,9 @@ private async Task Fetch(List assetIndex, CancellationToke .SetAllowedDecompression(DecompressionMethods.None) .Create(); + // Get the instance of a new DownloadClient + DownloadClient downloadClient = DownloadClient.CreateInstance(client); + // Use HttpClient instance on fetching using Http _httpClient = new Http(true, 5, 1000, _userAgent, client); try @@ -100,9 +104,6 @@ private async Task Fetch(List assetIndex, CancellationToke SenadinaFileIdentifier? patchConfigManifestSenadinaFileIdentifier = null; _mainMetaRepoUrl = null; - // Get the instance of the inner HttpClient from Hi3Helper.Http - HttpClient httpClient = _httpClient.GetHttpClient()!; - // Get the status if the current game is Senadina version. GameTypeHonkaiVersion gameVersionKind = _gameVersionManager!.CastAs()!; int[] versionArray = gameVersionKind.GetGameVersionAPI()?.VersionArray!; @@ -114,16 +115,16 @@ private async Task Fetch(List assetIndex, CancellationToke _mainMetaRepoUrl = $"https://r2.bagelnl.my.id/cl-meta/pustaka/{_gameVersionManager!.GamePreset!.ProfileName}/{string.Join('.', versionArray)}"; // Get the Senadina File Identifier Dictionary and its file references - senadinaFileIdentifier = await GetSenadinaIdentifierDictionary(httpClient, _mainMetaRepoUrl, token); - audioManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(httpClient, senadinaFileIdentifier, + senadinaFileIdentifier = await GetSenadinaIdentifierDictionary(client, _mainMetaRepoUrl, token); + audioManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier, SenadinaKind.chiptunesCurrent, versionArray, _mainMetaRepoUrl, false, token); - blocksPlatformManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(httpClient, senadinaFileIdentifier, + blocksPlatformManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier, SenadinaKind.platformBase, versionArray, _mainMetaRepoUrl, false, token); - blocksBaseManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(httpClient, senadinaFileIdentifier, + blocksBaseManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier, SenadinaKind.bricksBase, versionArray, _mainMetaRepoUrl, false, token); - blocksCurrentManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(httpClient, senadinaFileIdentifier, + blocksCurrentManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier, SenadinaKind.bricksCurrent, versionArray, _mainMetaRepoUrl, false, token); - patchConfigManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(httpClient, senadinaFileIdentifier, + patchConfigManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier, SenadinaKind.wandCurrent, versionArray, _mainMetaRepoUrl, true, token); } @@ -135,13 +136,13 @@ private async Task Fetch(List assetIndex, CancellationToke // Region: VideoIndex via External -> _cacheUtil: Data Fetch // Fetch video index and also fetch the gateway URL (string, string) gatewayURL; - gatewayURL = await FetchVideoAndGateway(_httpClient, assetIndex, IgnoredAssetIDs, token); + gatewayURL = await FetchVideoAndGateway(downloadClient, assetIndex, IgnoredAssetIDs, token); _assetBaseURL = "http://" + gatewayURL.Item1 + '/'; _gameServer = _cacheUtil?.GetCurrentGateway()!; // Region: AudioIndex // Try check audio manifest.m file and fetch it if it doesn't exist - await FetchAudioIndex(httpClient, assetIndex, IgnoredAssetIDs, audioManifestSenadinaFileIdentifier!, token); + await FetchAudioIndex(client, assetIndex, IgnoredAssetIDs, audioManifestSenadinaFileIdentifier!, token); } // Assign the URL based on the version @@ -155,7 +156,7 @@ private async Task Fetch(List assetIndex, CancellationToke { // Region: XMFAndAssetIndex // Try check XMF file and fetch it if it doesn't exist - await FetchXMFFile(httpClient, assetIndex, blocksPlatformManifestSenadinaFileIdentifier, + await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentifier, blocksBaseManifestSenadinaFileIdentifier!, blocksCurrentManifestSenadinaFileIdentifier!, patchConfigManifestSenadinaFileIdentifier!, manifestDict[_gameVersion.VersionString!], token); @@ -275,10 +276,10 @@ private HonkaiRepairAssetIgnore GetIgnoredAssetsProperty() #endregion #region VideoIndex via External -> _cacheUtil: Data Fetch - private async Task<(string, string)> FetchVideoAndGateway(Http _httpClient, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, CancellationToken token) + private async Task<(string, string)> FetchVideoAndGateway(DownloadClient downloadClient, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, CancellationToken token) { // Fetch data cache file only and get the gateway - (List, string, string, int) cacheProperty = await _cacheUtil!.GetCacheAssetList(_httpClient, CacheAssetType.Data, token); + (List, string, string, int) cacheProperty = await _cacheUtil!.GetCacheAssetList(downloadClient, CacheAssetType.Data, token); if (!_isOnlyRecoverMain) { @@ -286,23 +287,23 @@ private HonkaiRepairAssetIgnore GetIgnoredAssetsProperty() CacheAsset cacheAsset = cacheProperty.Item1.Where(x => x!.N!.EndsWith($"{HashID.CGMetadata}")).FirstOrDefault(); // Deserialize and build video index into asset index - await BuildVideoIndex(_httpClient, cacheAsset, cacheProperty.Item2, assetIndex, ignoredAssetIDs, cacheProperty.Item4, token); + await BuildVideoIndex(downloadClient, cacheAsset, cacheProperty.Item2, assetIndex, ignoredAssetIDs, cacheProperty.Item4, token); } // Return the gateway URL including asset bundle and asset cache return (cacheProperty.Item2, cacheProperty.Item3); } - private async Task BuildVideoIndex(Http _httpClient, CacheAsset cacheAsset, string assetBundleURL, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, int luckyNumber, CancellationToken token) + private async Task BuildVideoIndex(DownloadClient downloadClient, CacheAsset cacheAsset, string assetBundleURL, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, int luckyNumber, CancellationToken token) { // Get the remote stream and use CacheStream using (Stream memoryStream = new MemoryStream()) { - if (_httpClient == null) throw new ObjectDisposedException("RepairManagement::Honkai::Fetch:BuildVideoIndex() error!" + - "\r\n _httpClient is unexpectedly disposed."); + if (downloadClient == null) throw new ObjectDisposedException("RepairManagement::Honkai::Fetch:BuildVideoIndex() error!" + + "\r\n downloadClient is unexpectedly disposed."); ArgumentNullException.ThrowIfNull(cacheAsset); // Download the cache and store it to MemoryStream - await _httpClient.Download(cacheAsset.ConcatURL, memoryStream, null, null, token); + await downloadClient.DownloadAsync(cacheAsset.ConcatURL, memoryStream, false, cancelToken: token); memoryStream.Position = 0; // Use CacheStream to decrypt and read it as Stream @@ -391,7 +392,7 @@ private async ValueTask IsCGFileAvailable(CGMetadata cgInfo, string baseUR #endregion #region AudioIndex - private async Task FetchAudioIndex(HttpClient _httpClient, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, SenadinaFileIdentifier senadinaFileIdentifier, CancellationToken token) + private async Task FetchAudioIndex(HttpClient httpClient, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, SenadinaFileIdentifier senadinaFileIdentifier, CancellationToken token) { // If the gameServer is null, then just leave if (_gameServer == null) @@ -408,7 +409,7 @@ private async Task FetchAudioIndex(HttpClient _httpClient, List assetIndex, CancellationToke .SetAllowedDecompression(DecompressionMethods.None) .Create(); + // Initialize the new DownloadClient + DownloadClient downloadClient = DownloadClient.CreateInstance(client); + try { // Get the primary manifest @@ -97,24 +101,24 @@ private async Task Fetch(List assetIndex, CancellationToke } // Subscribe the fetching progress and subscribe StarRailMetadataTool progress to adapter - _innerGameVersionManager.StarRailMetadataTool.HttpEvent += _httpClient_FetchAssetProgress; + // _innerGameVersionManager.StarRailMetadataTool.HttpEvent += _httpClient_FetchAssetProgress; // Initialize the metadata tool (including dispatcher and gateway). // Perform this if only base._isVersionOverride is false to indicate that the repair performed is // not for delta patch integrity check. - if (!base._isVersionOverride && !this._isOnlyRecoverMain && await _innerGameVersionManager.StarRailMetadataTool.Initialize(token, GetExistingGameRegionID(), Path.Combine(_gamePath, $"{Path.GetFileNameWithoutExtension(_innerGameVersionManager.GamePreset.GameExecutableName)}_Data\\Persistent"))) + if (!base._isVersionOverride && !this._isOnlyRecoverMain && await _innerGameVersionManager.StarRailMetadataTool.Initialize(token, downloadClient, _httpClient_FetchAssetProgress, GetExistingGameRegionID(), Path.Combine(_gamePath, $"{Path.GetFileNameWithoutExtension(_innerGameVersionManager.GamePreset.GameExecutableName)}_Data\\Persistent"))) { // Read block metadata and convert to FilePropertiesRemote - await _innerGameVersionManager.StarRailMetadataTool.ReadAsbMetadataInformation(token); - await _innerGameVersionManager.StarRailMetadataTool.ReadBlockMetadataInformation(token); + await _innerGameVersionManager.StarRailMetadataTool.ReadAsbMetadataInformation(downloadClient, _httpClient_FetchAssetProgress, token); + await _innerGameVersionManager.StarRailMetadataTool.ReadBlockMetadataInformation(downloadClient, _httpClient_FetchAssetProgress, token); ConvertSRMetadataToAssetIndex(_innerGameVersionManager.StarRailMetadataTool.MetadataBlock, assetIndex); // Read Audio metadata and convert to FilePropertiesRemote - await _innerGameVersionManager.StarRailMetadataTool.ReadAudioMetadataInformation(token); + await _innerGameVersionManager.StarRailMetadataTool.ReadAudioMetadataInformation(downloadClient, _httpClient_FetchAssetProgress, token); ConvertSRMetadataToAssetIndex(_innerGameVersionManager.StarRailMetadataTool.MetadataAudio, assetIndex, true); // Read Video metadata and convert to FilePropertiesRemote - await _innerGameVersionManager.StarRailMetadataTool.ReadVideoMetadataInformation(token); + await _innerGameVersionManager.StarRailMetadataTool.ReadVideoMetadataInformation(downloadClient, _httpClient_FetchAssetProgress, token); ConvertSRMetadataToAssetIndex(_innerGameVersionManager.StarRailMetadataTool.MetadataVideo, assetIndex); } @@ -133,7 +137,7 @@ private async Task Fetch(List assetIndex, CancellationToke // Clear the hashtable StarRailRepairExtension.ClearHashtable(); // Unsubscribe the fetching progress and dispose it and unsubscribe cacheUtil progress to adapter - _innerGameVersionManager.StarRailMetadataTool.HttpEvent -= _httpClient_FetchAssetProgress; + // _innerGameVersionManager.StarRailMetadataTool.HttpEvent -= _httpClient_FetchAssetProgress; } } diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs index ead6957f5..5b6fb955e 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index faf6ecb04..019afdf62 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -1,6 +1,6 @@ using CollapseLauncher.Helper.Update; using Hi3Helper; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs index 32f279f39..ada8c5008 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs @@ -6,7 +6,7 @@ using CollapseLauncher.Statics; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; diff --git a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs index 6c30691d7..b2e79a8b7 100644 --- a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs +++ b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Update; using Hi3Helper; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Squirrel; using Squirrel.Sources; using System; diff --git a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs index babcff693..e0af9ea89 100644 --- a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Update; using Hi3Helper; -using Hi3Helper.Http; +using Hi3Helper.Http.Legacy; using Microsoft.UI.Xaml; using System; using System.Diagnostics; diff --git a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs index 483cd253d..7999a11e3 100644 --- a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs +++ b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs @@ -13,7 +13,7 @@ namespace Hi3Helper.Data { public class GenshinDispatchHelper : IDisposable { - private Http.Http _httpClient; + private Http.Legacy.Http _httpClient; private string DispatchBaseURL { get; set; } private string RegionSubdomain { get; set; } private string ChannelName = "OSRELWin"; @@ -26,7 +26,7 @@ public class GenshinDispatchHelper : IDisposable public GenshinDispatchHelper(int RegionID, string DispatchKey, string DispatchURLPrefix, string VersionString = "2.6.0", CancellationToken cancelToken = new CancellationToken()) { if (RegionID >= 4) ChannelName = "CNRELWin"; - this._httpClient = new Http.Http(false, 1, 1); + this._httpClient = new Http.Legacy.Http(false, 1, 1); this.RegionSubdomain = GetSubdomainByRegionID(RegionID); this.Version = VersionString; this.DispatchBaseURL = string.Format(DispatchURLPrefix!, RegionSubdomain, $"{ChannelName}{VersionString}", DispatchKey); diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index f9dc16bb6..d3ded7209 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit f9dc16bb65f11b36fc3f04bcbcd59057cb3979d7 +Subproject commit d3ded72092e2076d6e4d6cb336d4c8e0589763bd diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 897827cdb..f7a684b3d 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 897827cdb2b220f967dc9170434818e8ed35e1ea +Subproject commit f7a684b3da7587d654da1996aa354e387f2a7994 From ba96d7c6a9f08e453a8239eebd689ff3889f0d36 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 00:01:22 +0700 Subject: [PATCH 02/25] Change ``DownloadProgress`` as ``struct`` --- .../Classes/Interfaces/Class/ProgressBase.cs | 24 +++++++++---------- Hi3Helper.Http | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index e54219cd5..1dee48e9b 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -81,13 +81,13 @@ private void Init() protected void _innerObject_ProgressAdapter(object sender, TotalPerfileProgress e) => ProgressChanged?.Invoke(sender, e); protected void _innerObject_StatusAdapter(object sender, TotalPerfileStatus e) => StatusChanged?.Invoke(sender, e); - protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadProgress downloadData) + protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadProgress downloadProgress) { if (await CheckIfNeedRefreshStopwatch()) { - double speed = downloadData.BytesDownloaded / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; - TimeSpan timeLeftSpan = ((downloadData.BytesTotal - downloadData.BytesDownloaded) / speed).ToTimeSpanNormalized(); - double percentage = ConverterTool.GetPercentageNumber(downloadData.BytesDownloaded, downloadData.BytesTotal, 2); + double speed = downloadProgress.BytesDownloaded / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + TimeSpan timeLeftSpan = ((downloadProgress.BytesTotal - downloadProgress.BytesDownloaded) / speed).ToTimeSpanNormalized(); + double percentage = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal, 2); lock (_status!) { @@ -101,8 +101,8 @@ protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadPr { // Update fetch progress _progress.ProgressPerFilePercentage = percentage; - _progress.ProgressAllSizeCurrent = downloadData.BytesDownloaded; - _progress.ProgressAllSizeTotal = downloadData.BytesTotal; + _progress.ProgressAllSizeCurrent = downloadProgress.BytesDownloaded; + _progress.ProgressAllSizeTotal = downloadProgress.BytesTotal; _progress.ProgressAllSpeed = speed; _progress.ProgressAllTimeLeft = timeLeftSpan; } @@ -144,11 +144,11 @@ protected virtual async void _httpClient_RepairAssetProgress(object sender, Down { lock (_progress!) { - _progress.ProgressPerFilePercentage = e!.ProgressPercentage; - _progress.ProgressPerFileSizeCurrent = e!.SizeDownloaded; + _progress.ProgressPerFilePercentage = e!.ProgressPercentage; + _progress.ProgressPerFileSizeCurrent = e!.SizeDownloaded; _progress.ProgressPerFileSizeTotal = e!.SizeToBeDownloaded; - _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; - _progress.ProgressAllSizeTotal = _progressAllSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate speed long speed = (long)(_progressAllSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); @@ -179,8 +179,8 @@ protected virtual async void _httpClient_RepairAssetProgress(object sender, Down string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress!.ProgressAllTimeLeft); _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressAllSpeed)); - _status.ActivityAll = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, - ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), + _status.ActivityAll = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, + ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)) + $" | {timeLeftString}"; // Trigger update diff --git a/Hi3Helper.Http b/Hi3Helper.Http index f7a684b3d..f027e9ac2 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit f7a684b3da7587d654da1996aa354e387f2a7994 +Subproject commit f027e9ac29ef05f8a22aeb6392950b98bd7fa698 From 68434381f0a49fd9b32d5fb033f77b098e196cd6 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 00:27:32 +0700 Subject: [PATCH 03/25] Moving Legacy ``Http()`` to ``DownloadClient()`` (phase 2) HSR Game Repair and Cache Update --- .../CachesManagement/StarRail/Update.cs | 67 ++-------- .../Classes/Interfaces/Class/ProgressBase.cs | 120 +++++++++++++++++- .../RepairManagement/StarRail/Fetch.cs | 14 +- .../RepairManagement/StarRail/Repair.cs | 100 +++++++-------- 4 files changed, 180 insertions(+), 121 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index 3b0e1219c..cc4da3dd1 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -1,8 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; -using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetMetadata.SRMetadataAsset; -using Hi3Helper.Http.Legacy; +using Hi3Helper.Http; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -30,16 +29,14 @@ private async Task Update(List updateAssetIndex, List as .SetAllowedDecompression(DecompressionMethods.None) .Create(); - // Assign Http client - Http httpClient = new Http(true, 5, 1000, _userAgent, client); + // Assign DownloadClient + DownloadClient downloadClient = DownloadClient.CreateInstance(client); try { // Set IsProgressAllIndetermined as false and update the status _status.IsProgressAllIndetermined = true; UpdateStatus(); - // Subscribe the event listener - httpClient.DownloadProgress += _httpClient_UpdateAssetProgress; // Iterate the asset index and do update operation ObservableCollection assetProperty = new ObservableCollection(AssetEntry); if (_isBurstDownloadEnabled) @@ -55,7 +52,7 @@ await Parallel.ForEachAsync( new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, async (asset, innerToken) => { - await UpdateCacheAsset(asset, httpClient, innerToken); + await UpdateCacheAsset(asset, downloadClient, _httpClient_UpdateAssetProgress, innerToken); }); } else @@ -69,7 +66,7 @@ await Parallel.ForEachAsync( #endif , assetProperty)) { - await UpdateCacheAsset(asset, httpClient, token); + await UpdateCacheAsset(asset, downloadClient, _httpClient_UpdateAssetProgress, token); } } @@ -82,15 +79,9 @@ await Parallel.ForEachAsync( LogWriteLine($"An error occured while updating cache file!\r\n{ex}", LogType.Error, true); throw; } - finally - { - // Unsubscribe the event listener and dispose Http client - httpClient.DownloadProgress -= _httpClient_UpdateAssetProgress; - httpClient.Dispose(); - } } - private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetProperty) asset, Http httpClient, CancellationToken token) + private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Increment total count and update the status _progressAllCountCurrent++; @@ -104,17 +95,14 @@ private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetPro Directory.CreateDirectory(assetDir); } - // Do multi-session download for asset that has applicable size - if (asset.AssetIndex.Size >= _sizeForMultiDownload && !_isBurstDownloadEnabled) - { - await httpClient.Download(asset.AssetIndex.RemoteURL, asset.AssetIndex.LocalName, _downloadThreadCount, true, token); - await httpClient.Merge(token); - } - // Do single-session download for others - else - { - await httpClient.Download(asset.AssetIndex.RemoteURL, asset.AssetIndex.LocalName, true, null, null, token); - } + // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) + await downloadClient.DownloadAsync( + asset.AssetIndex.RemoteURL, + asset.AssetIndex.LocalName, + true, + progressDelegateAsync: downloadProgress, + cancelToken: token + ); LogWriteLine($"Downloaded cache [T: {asset.AssetIndex.AssetType}]: {Path.GetFileName(asset.AssetIndex.LocalName)}", LogType.Default, true); @@ -122,32 +110,5 @@ private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetPro // Remove Asset Entry display PopRepairAssetEntry(asset.AssetProperty); } - - private async void _httpClient_UpdateAssetProgress(object sender, DownloadEvent e) - { - // Update current progress percentages and speed - _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : - 0; - - if (e.State != DownloadState.Merging) - { - _progressAllSizeCurrent += e.Read; - } - long speed = (long)(_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds); - - if (await CheckIfNeedRefreshStopwatch()) - { - // Update current activity status - _status.IsProgressAllIndetermined = false; - string timeLeftString = string.Format(Lang._Misc.TimeRemainHMSFormat, ((_progressAllSizeCurrent - _progressAllSizeTotal) / ConverterTool.Unzeroed(speed)).ToTimeSpanNormalized()); - _status.ActivityAll = string.Format(Lang._Misc.Downloading + ": {0}/{1} ", _progressAllCountCurrent, _progressAllCountTotal) - + string.Format($"({Lang._Misc.SpeedPerSec})", ConverterTool.SummarizeSizeSimple(speed)) - + $" | {timeLeftString}"; - - // Trigger update - UpdateAll(); - } - } } } diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 1dee48e9b..637394d07 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -85,7 +85,7 @@ protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadPr { if (await CheckIfNeedRefreshStopwatch()) { - double speed = downloadProgress.BytesDownloaded / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speed = downloadProgress.BytesDownloaded / _stopwatch.Elapsed.TotalSeconds; TimeSpan timeLeftSpan = ((downloadProgress.BytesTotal - downloadProgress.BytesDownloaded) / speed).ToTimeSpanNormalized(); double percentage = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal, 2); @@ -140,6 +140,53 @@ protected virtual void _httpClient_FetchAssetProgress(object sender, DownloadEve #endregion #region ProgressEventHandlers - Repair + protected virtual async void _httpClient_RepairAssetProgress(int size, DownloadProgress downloadProgress) + { + Interlocked.Add(ref _progressAllSizeCurrent, size); + if (await CheckIfNeedRefreshStopwatch()) + { + double speed = _progressAllSizeCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + TimeSpan timeLeftSpan = ((_progressAllSizeCurrent - _progressAllSizeTotal) / speed).ToTimeSpanNormalized(); + double percentage = ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal, 2); + + lock (_progress!) + { + _progress.ProgressPerFilePercentage = percentage; + _progress.ProgressPerFileSizeCurrent = downloadProgress.BytesDownloaded; + _progress.ProgressPerFileSizeTotal = downloadProgress.BytesTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; + + // Calculate speed + _progress.ProgressAllSpeed = speed; + _progress.ProgressAllTimeLeft = timeLeftSpan; + + // Update current progress percentages + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : + 0; + } + + lock (_status!) + { + // Update current activity status + _status.IsProgressAllIndetermined = false; + _status.IsProgressPerFileIndetermined = false; + + // Set time estimation string + string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress!.ProgressAllTimeLeft); + + _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressAllSpeed)); + _status.ActivityAll = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, + ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), + ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)) + $" | {timeLeftString}"; + + // Trigger update + UpdateAll(); + } + } + } + protected virtual async void _httpClient_RepairAssetProgress(object sender, DownloadEvent e) { lock (_progress!) @@ -204,6 +251,60 @@ protected virtual void UpdateRepairStatus(string activityStatus, string Activity } #endregion + #region ProgressEventHandlers - UpdateCache + protected virtual async void _httpClient_UpdateAssetProgress(int size, DownloadProgress downloadProgress) + { + Interlocked.Add(ref _progressAllSizeCurrent, size); + + if (await CheckIfNeedRefreshStopwatch()) + { + double speed = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; + TimeSpan timeLeftSpan = ((_progressAllSizeTotal - _progressAllSizeCurrent) / speed).ToTimeSpanNormalized(); + double percentage = ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal, 2); + + // Update current progress percentages and speed + _progress.ProgressAllPercentage = percentage; + + // Update current activity status + _status.IsProgressAllIndetermined = false; + string timeLeftString = string.Format(Lang._Misc.TimeRemainHMSFormat, timeLeftSpan); + _status.ActivityAll = string.Format(Lang._Misc.Downloading + ": {0}/{1} ", _progressAllCountCurrent, _progressAllCountTotal) + + string.Format($"({Lang._Misc.SpeedPerSec})", ConverterTool.SummarizeSizeSimple(speed)) + + $" | {timeLeftString}"; + + // Trigger update + UpdateAll(); + } + } + + protected virtual async void _httpClient_UpdateAssetProgress(object sender, DownloadEvent e) + { + // Update current progress percentages and speed + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : + 0; + + if (e.State != DownloadState.Merging) + { + _progressAllSizeCurrent += e.Read; + } + long speed = (long)(_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds); + + if (await CheckIfNeedRefreshStopwatch()) + { + // Update current activity status + _status.IsProgressAllIndetermined = false; + string timeLeftString = string.Format(Lang._Misc.TimeRemainHMSFormat, ((_progressAllSizeCurrent - _progressAllSizeTotal) / ConverterTool.Unzeroed(speed)).ToTimeSpanNormalized()); + _status.ActivityAll = string.Format(Lang._Misc.Downloading + ": {0}/{1} ", _progressAllCountCurrent, _progressAllCountTotal) + + string.Format($"({Lang._Misc.SpeedPerSec})", ConverterTool.SummarizeSizeSimple(speed)) + + $" | {timeLeftString}"; + + // Trigger update + UpdateAll(); + } + } + #endregion + #region ProgressEventHandlers - Patch protected virtual async void RepairTypeActionPatching_ProgressChanged(object sender, BinaryPatchProgress e) { @@ -849,6 +950,23 @@ protected bool SummarizeStatusAndProgress(List assetIndex, string msgIfFound } protected virtual bool IsArrayMatch(ReadOnlySpan source, ReadOnlySpan target) => source.SequenceEqual(target); + + protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) + { + // Check for directory availability + string dirPath = Path.GetDirectoryName(assetPath); + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + + // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) + await downloadClient.DownloadAsync( + assetURL, + assetPath, + true, + progressDelegateAsync: downloadProgress, + cancelToken: token + ); + } protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, Http _httpClient, CancellationToken token) { diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index 768c56cba..8632c0324 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -4,7 +4,6 @@ using Hi3Helper.EncTool.Parser.AssetIndex; using Hi3Helper.EncTool.Parser.AssetMetadata.SRMetadataAsset; using Hi3Helper.Http; -using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Shared.Region; using System; @@ -84,8 +83,7 @@ private async Task Fetch(List assetIndex, CancellationToke try { // Get the primary manifest - using Http httpClient = new Http(client); - await GetPrimaryManifest(httpClient, token, assetIndex); + await GetPrimaryManifest(downloadClient, token, assetIndex); // If the this._isOnlyRecoverMain && base._isVersionOverride is true, copy the asset index into the _originAssetIndex if (this._isOnlyRecoverMain && base._isVersionOverride) @@ -153,7 +151,7 @@ private void EliminatePluginAssetIndex(List assetIndex) } #region PrimaryManifest - private async Task GetPrimaryManifest(Http client, CancellationToken token, List assetIndex) + private async Task GetPrimaryManifest(DownloadClient downloadClient, CancellationToken token, List assetIndex) { // Initialize pkgVersion list List pkgVersion = new List(); @@ -221,8 +219,7 @@ private async Task GetPrimaryManifest(Http client, CancellationToken token, List // Try get the data using MemoryStream ms = new MemoryStream(); using StreamReader sr = new StreamReader(ms); - client.DownloadProgress += _httpClient_FetchAssetProgress; - await client.Download(basePkgVersionUrl, ms, null, null, token); + await downloadClient.DownloadAsync(basePkgVersionUrl, ms, false, _httpClient_FetchAssetProgress); // Read the stream and deserialize the JSON pkgVersion.Clear(); @@ -235,11 +232,6 @@ private async Task GetPrimaryManifest(Http client, CancellationToken token, List } } catch { throw; } - finally - { - // Unsubscribe the event - client.DownloadProgress -= _httpClient_FetchAssetProgress; - } } // Convert the pkg version list to asset index diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs index 5b6fb955e..d9bd0076e 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http.Legacy; +using Hi3Helper.Http; using Hi3Helper.Shared.ClassStruct; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -37,79 +37,67 @@ private async Task Repair(List repairAssetIndex, Can .SetAllowedDecompression(DecompressionMethods.None) .Create(); - // Use HttpClient instance on fetching - using Http _httpClient = new Http(true, 5, 1000, _userAgent, client); + // Use the new DownloadClient instance + DownloadClient downloadClient = DownloadClient.CreateInstance(client); - // Try running instance - try + // Iterate repair asset and check it using different method for each type + ObservableCollection assetProperty = new ObservableCollection(AssetEntry); + if (_isBurstDownloadEnabled) { - // Assign downloader event - _httpClient.DownloadProgress += _httpClient_RepairAssetProgress; - - // Iterate repair asset and check it using different method for each type - ObservableCollection assetProperty = new ObservableCollection(AssetEntry); - if (_isBurstDownloadEnabled) - { - await Parallel.ForEachAsync( - PairEnumeratePropertyAndAssetIndexPackage( -#if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) -#else - repairAssetIndex -#endif - , assetProperty), - new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, - async (asset, innerToken) => - { - // Assign a task depends on the asset type - Task assetTask = asset.AssetIndex.FT switch - { - FileType.Blocks => RepairAssetTypeGeneric(asset, _httpClient, innerToken), - FileType.Audio => RepairAssetTypeGeneric(asset, _httpClient, innerToken), - FileType.Video => RepairAssetTypeGeneric(asset, _httpClient, innerToken), - _ => RepairAssetTypeGeneric(asset, _httpClient, innerToken) - }; - - // Await the task - await assetTask; - }); - } - else - { - foreach ((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset in - PairEnumeratePropertyAndAssetIndexPackage( + await Parallel.ForEachAsync( + PairEnumeratePropertyAndAssetIndexPackage( #if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) #else - repairAssetIndex + repairAssetIndex #endif - , assetProperty)) + , assetProperty), + new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, + async (asset, innerToken) => { // Assign a task depends on the asset type Task assetTask = asset.AssetIndex.FT switch { - FileType.Blocks => RepairAssetTypeGeneric(asset, _httpClient, token), - FileType.Audio => RepairAssetTypeGeneric(asset, _httpClient, token), - FileType.Video => RepairAssetTypeGeneric(asset, _httpClient, token), - _ => RepairAssetTypeGeneric(asset, _httpClient, token) + FileType.Blocks => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + FileType.Audio => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + FileType.Video => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + _ => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken) }; // Await the task await assetTask; - } - } - - return true; + }); } - finally + else { - // Unassign downloader event - _httpClient.DownloadProgress -= _httpClient_RepairAssetProgress; + foreach ((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset in + PairEnumeratePropertyAndAssetIndexPackage( +#if ENABLEHTTPREPAIR + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) +#else + repairAssetIndex +#endif + , assetProperty)) + { + // Assign a task depends on the asset type + Task assetTask = asset.AssetIndex.FT switch + { + FileType.Blocks => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token), + FileType.Audio => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token), + FileType.Video => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token), + _ => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token) + }; + + // Await the task + await assetTask; + } } + + return true; } #region GenericRepair - private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Increment total count current _progressAllCountCurrent++; @@ -134,7 +122,7 @@ private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAss else { // Start asset download task - await RunDownloadTask(asset.AssetIndex.S, asset.AssetIndex.N, asset.AssetIndex.RN, _httpClient, token); + await RunDownloadTask(asset.AssetIndex.S, asset.AssetIndex.N, asset.AssetIndex.RN, downloadClient, downloadProgress, token); LogWriteLine($"File [T: {asset.AssetIndex.FT}] {(asset.AssetIndex.FT == FileType.Blocks ? asset.AssetIndex.CRC : asset.AssetIndex.N)} has been downloaded!", LogType.Default, true); } From e1767c08120f2781175205098067dfdf48beaa92 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 00:42:24 +0700 Subject: [PATCH 04/25] Moving Legacy ``Http()`` to ``DownloadClient()`` (phase 3 - Hi3) Honkai Impact 3rd Game Repair and Cache Update --- .../Classes/CachesManagement/Honkai/Fetch.cs | 68 ++++------ .../Classes/CachesManagement/Honkai/Update.cs | 59 +-------- .../Classes/Interfaces/Class/ProgressBase.cs | 7 +- .../Classes/RepairManagement/Honkai/Fetch.cs | 13 +- .../Classes/RepairManagement/Honkai/Repair.cs | 123 ++++++++---------- Hi3Helper.Http | 2 +- 6 files changed, 98 insertions(+), 174 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index 0d9bb963e..9b731a96a 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -5,7 +5,6 @@ using Hi3Helper.EncTool; using Hi3Helper.EncTool.Parser.KianaDispatch; using Hi3Helper.Http; -using Hi3Helper.Http.Legacy; using Hi3Helper.UABT; using System; using System.Collections.Generic; @@ -38,50 +37,37 @@ private async Task> Fetch(CancellationToken token) // Use a new DownloadClient for fetching DownloadClient downloadClient = DownloadClient.CreateInstance(httpClientNew); - // Use HttpClient instance on fetching - using Http httpClient = new Http(true, 5, 1000, _userAgent, httpClientNew); - try - { - // Subscribe the event listener - httpClient.DownloadProgress += _httpClient_FetchAssetProgress; - - // Build _gameRepoURL from loading Dispatcher and Gateway - await BuildGameRepoURL(downloadClient, token); + // Build _gameRepoURL from loading Dispatcher and Gateway + await BuildGameRepoURL(downloadClient, token); - // Iterate type and do fetch - foreach (CacheAssetType type in Enum.GetValues()) + // Iterate type and do fetch + foreach (CacheAssetType type in Enum.GetValues()) + { + // Skip for unused type + switch (type) { - // Skip for unused type - switch (type) - { - case CacheAssetType.Unused: - case CacheAssetType.Dispatcher: - case CacheAssetType.Gateway: - case CacheAssetType.General: - case CacheAssetType.IFix: - case CacheAssetType.DesignData: - case CacheAssetType.Lua: - continue; - } + case CacheAssetType.Unused: + case CacheAssetType.Dispatcher: + case CacheAssetType.Gateway: + case CacheAssetType.General: + case CacheAssetType.IFix: + case CacheAssetType.DesignData: + case CacheAssetType.Lua: + continue; + } - // uint = Count of the assets available - // long = Total size of the assets available - (int, long) count = await FetchByType(type, downloadClient, returnAsset, token); + // uint = Count of the assets available + // long = Total size of the assets available + (int, long) count = await FetchByType(type, downloadClient, returnAsset, token); - // Write a log about the metadata - LogWriteLine($"Cache Metadata [T: {type}]:", LogType.Default, true); - LogWriteLine($" Cache Count = {count.Item1}", LogType.NoTag, true); - LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); + // Write a log about the metadata + LogWriteLine($"Cache Metadata [T: {type}]:", LogType.Default, true); + LogWriteLine($" Cache Count = {count.Item1}", LogType.NoTag, true); + LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); - // Increment the Total Size and Count - _progressAllCountTotal += count.Item1; - _progressAllSizeTotal += count.Item2; - } - } - finally - { - // Unsubscribe the event listener and dispose Http client - httpClient.DownloadProgress -= _httpClient_FetchAssetProgress; + // Increment the Total Size and Count + _progressAllCountTotal += count.Item1; + _progressAllSizeTotal += count.Item2; } // Return asset index @@ -149,7 +135,7 @@ private async Task BuildGameRepoURL(DownloadClient downloadClient, CancellationT // Get a direct HTTP Stream await using HttpResponseInputStream remoteStream = await HttpResponseInputStream.CreateStreamAsync( - downloadClient.GetHttpClient(), assetIndexURL, null, null, token); + downloadClient.GetHttpClient(), assetIndexURL, null, null, null, null, null, token); using XORStream stream = new XORStream(remoteStream); diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index 900297c28..93d2536cc 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -1,8 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Interfaces; using Hi3Helper; -using Hi3Helper.Data; -using Hi3Helper.Http.Legacy; +using Hi3Helper.Http; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -27,16 +26,14 @@ private async Task Update(List updateAssetIndex, List assetProperty = new ObservableCollection(AssetEntry); if (_isBurstDownloadEnabled) @@ -52,7 +49,7 @@ await Parallel.ForEachAsync( new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, async (asset, innerToken) => { - await UpdateCacheAsset(asset, httpClient, innerToken); + await UpdateCacheAsset(asset, downloadClient, _httpClient_UpdateAssetProgress, innerToken); }); } else @@ -66,7 +63,7 @@ await Parallel.ForEachAsync( #endif , assetProperty)) { - await UpdateCacheAsset(asset, httpClient, token); + await UpdateCacheAsset(asset, downloadClient, _httpClient_UpdateAssetProgress, token); } } @@ -82,11 +79,6 @@ await Parallel.ForEachAsync( LogWriteLine($"An error occured while updating cache file!\r\n{ex}", LogType.Error, true); throw; } - finally - { - // Unsubscribe the event listener and dispose Http client - httpClient.DownloadProgress -= _httpClient_UpdateAssetProgress; - } } private void UpdateCacheVerifyList(List assetIndex) @@ -110,7 +102,7 @@ private void UpdateCacheVerifyList(List assetIndex) } } - private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty AssetProperty) asset, Http httpClient, CancellationToken token) + private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Increment total count and update the status _progressAllCountCurrent++; @@ -143,17 +135,7 @@ private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty Asset LogWriteLine($"Downloading cache [T: {asset.AssetIndex.DataType}]: {asset.AssetIndex.N} at URL: {asset.AssetIndex.ConcatURL}", LogType.Debug, true); #endif - // Do multi-session download for asset that has applicable size - if (asset.AssetIndex.CS >= _sizeForMultiDownload && !_isBurstDownloadEnabled) - { - await httpClient!.Download(asset.AssetIndex.ConcatURL, asset.AssetIndex.ConcatPath, _downloadThreadCount, true, token); - await httpClient.Merge(token); - } - // Do single-session download for others - else - { - await httpClient!.Download(asset.AssetIndex.ConcatURL, asset.AssetIndex.ConcatPath, true, null, null, token); - } + await RunDownloadTask(asset.AssetIndex.CS, asset.AssetIndex.ConcatPath, asset.AssetIndex.ConcatURL, downloadClient, downloadProgress, token); #if !DEBUG LogWriteLine($"Downloaded cache [T: {asset.AssetIndex.DataType}]: {asset.AssetIndex.N}", LogType.Default, true); @@ -163,32 +145,5 @@ private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty Asset // Remove Asset Entry display PopRepairAssetEntry(asset.AssetProperty); } - - private async void _httpClient_UpdateAssetProgress(object sender, DownloadEvent e) - { - // Update current progress percentages and speed - _progress!.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : - 0; - - if (e!.State != DownloadState.Merging) - { - _progressAllSizeCurrent += e.Read; - } - long speed = (long)(_progressAllSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); - - if (await CheckIfNeedRefreshStopwatch()) - { - // Update current activity status - _status!.IsProgressAllIndetermined = false; - string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, ((_progressAllSizeCurrent - _progressAllSizeTotal) / ConverterTool.Unzeroed(speed)).ToTimeSpanNormalized()); - _status.ActivityAll = string.Format(Lang!._Misc!.Downloading + ": {0}/{1} ", _progressAllCountCurrent, _progressAllCountTotal) - + string.Format($"({Lang._Misc.SpeedPerSec})", ConverterTool.SummarizeSizeSimple(speed)) - + $" | {timeLeftString}"; - - // Trigger update - UpdateAll(); - } - } } } diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 637394d07..37eab57c6 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -24,7 +24,6 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; using static Hi3Helper.Locale; using static Hi3Helper.Logger; @@ -1052,7 +1051,7 @@ protected virtual async ValueTask CheckHashAsync(Stream stream, HashAlgo #endregion #region PatchTools - protected virtual async ValueTask RunPatchTask(Http _httpClient, CancellationToken token, long patchSize, Memory patchHash, + protected virtual async ValueTask RunPatchTask(DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, long patchSize, Memory patchHash, string patchURL, string patchOutputFile, string inputFile, string outputFile, bool isNeedRename = false) { ArgumentNullException.ThrowIfNull(patchOutputFile); @@ -1066,7 +1065,7 @@ protected virtual async ValueTask RunPatchTask(Http _httpClient, CancellationTok if (!patchInfo.Exists || patchInfo.Length != patchSize) { // Download patch File first - await RunDownloadTask(patchSize, patchOutputFile, patchURL, _httpClient, token)!; + await RunDownloadTask(patchSize, patchOutputFile, patchURL, downloadClient, downloadProgress, token)!; } // Always do loop if patch doesn't get downloaded properly @@ -1082,7 +1081,7 @@ protected virtual async ValueTask RunPatchTask(Http _httpClient, CancellationTok _progressAllSizeCurrent -= patchSize; // Redownload the patch file - await RunDownloadTask(patchSize, patchOutputFile, patchURL, _httpClient, token)!; + await RunDownloadTask(patchSize, patchOutputFile, patchURL, downloadClient, downloadProgress, token)!; continue; } } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 74e49e1af..00af8f0a1 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -9,7 +9,6 @@ using Hi3Helper.EncTool.Parser.Cache; using Hi3Helper.EncTool.Parser.Senadina; using Hi3Helper.Http; -using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.Win32; using System; @@ -73,12 +72,9 @@ private async Task Fetch(List assetIndex, CancellationToke // Get the instance of a new DownloadClient DownloadClient downloadClient = DownloadClient.CreateInstance(client); - // Use HttpClient instance on fetching - using Http _httpClient = new Http(true, 5, 1000, _userAgent, client); try { // Subscribe the fetching progress and subscribe cacheUtil progress to adapter - _httpClient.DownloadProgress += _httpClient_FetchAssetProgress; _cacheUtil!.ProgressChanged += _innerObject_ProgressAdapter; _cacheUtil!.StatusChanged += _innerObject_StatusAdapter; @@ -88,7 +84,7 @@ private async Task Fetch(List assetIndex, CancellationToke // Region: XMFAndAssetIndex // Fetch metadata - Dictionary manifestDict = await FetchMetadata(_httpClient, token); + Dictionary manifestDict = await FetchMetadata(token); // Check for manifest. If it doesn't exist, then throw and warn the user if (!manifestDict.ContainsKey(_gameVersion.VersionString!)) @@ -168,7 +164,6 @@ await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentif finally { // Unsubscribe the fetching progress and dispose it and unsubscribe cacheUtil progress to adapter - _httpClient.DownloadProgress -= _httpClient_FetchAssetProgress; _cacheUtil!.ProgressChanged -= _innerObject_ProgressAdapter; _cacheUtil.StatusChanged -= _innerObject_StatusAdapter; senadinaFileIdentifier?.Clear(); @@ -178,7 +173,7 @@ await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentif private async Task?> GetSenadinaIdentifierDictionary(HttpClient client, string mainUrl, CancellationToken token) { string identifierUrl = CombineURLFromString(mainUrl, $"daftar-pustaka")!; - using Stream fileIdentifierStream = await HttpResponseInputStream.CreateStreamAsync(client, identifierUrl, null, null, token); + using Stream fileIdentifierStream = (await HttpResponseInputStream.CreateStreamAsync(client, identifierUrl, null, null, null, null, null, token))!; using Stream fileIdentifierStreamDecoder = new BrotliStream(fileIdentifierStream, CompressionMode.Decompress, true); await ThrowIfFileIsNotSenadina(fileIdentifierStream, token); @@ -210,7 +205,7 @@ await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentif } SenadinaFileIdentifier identifier = dict[origFileRelativePath]; - Stream networkStream = await HttpResponseInputStream.CreateStreamAsync(client, fileUrl, 0, null, token); + using Stream networkStream = (await HttpResponseInputStream.CreateStreamAsync(client, fileUrl, null, null, null, null, null, token))!; await ThrowIfFileIsNotSenadina(networkStream, token); identifier.fileStream = SenadinaFileIdentifier.CreateKangBakso(networkStream, identifier.lastIdentifier!, origFileRelativePath, (int)identifier.fileTime); @@ -539,7 +534,7 @@ private async Task TryGetAudioManifest(HttpClient client, Se #region XMFAndAssetIndex // ReSharper disable once UnusedParameter.Local - private async Task> FetchMetadata(Http _httpClient, CancellationToken token) + private async Task> FetchMetadata(CancellationToken token) { // Set metadata URL string urlMetadata = string.Format(AppGameRepoIndexURLPrefix, _gameVersionManager!.GamePreset!.ProfileName); diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs index d18a479fc..f916fc17f 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs @@ -1,7 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http.Legacy; +using Hi3Helper.Http; using Hi3Helper.Shared.ClassStruct; using System; using System.Collections.Generic; @@ -38,96 +38,85 @@ private async Task Repair(List repairAssetIndex, Can .SetAllowedDecompression(DecompressionMethods.None) .Create(); - // Use HttpClient instance on fetching - using Http _httpClient = new Http(true, 5, 1000, _userAgent, client); + // Initialize the new DownloadClient instance + DownloadClient downloadClient = DownloadClient.CreateInstance(client); - // Try running instance - try + // Iterate repair asset and check it using different method for each type + ObservableCollection assetProperty = new ObservableCollection(AssetEntry); + if (_isBurstDownloadEnabled) { - // Assign downloader event - _httpClient.DownloadProgress += _httpClient_RepairAssetProgress; - - // Iterate repair asset and check it using different method for each type - ObservableCollection assetProperty = new ObservableCollection(AssetEntry); - if (_isBurstDownloadEnabled) - { - await Parallel.ForEachAsync( - PairEnumeratePropertyAndAssetIndexPackage( -#if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) -#else - repairAssetIndex -#endif - , assetProperty), - new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, - async (asset, innerToken) => - { - // Assign a task depends on the asset type - Task assetTask = asset.AssetIndex.FT switch - { - FileType.Blocks => RepairAssetTypeBlocks(asset, _httpClient, innerToken), - FileType.Audio => RepairOrPatchTypeAudio(asset, _httpClient, innerToken), - FileType.Video => RepairAssetTypeVideo(asset, _httpClient, innerToken), - _ => RepairAssetTypeGeneric(asset, _httpClient, innerToken) - }; - - // Await the task - await assetTask; - }); - } - else - { - foreach ((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset in - PairEnumeratePropertyAndAssetIndexPackage( + await Parallel.ForEachAsync( + PairEnumeratePropertyAndAssetIndexPackage( #if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) #else - repairAssetIndex + repairAssetIndex #endif - , assetProperty)) + , assetProperty), + new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, + async (asset, innerToken) => { // Assign a task depends on the asset type Task assetTask = asset.AssetIndex.FT switch { - FileType.Blocks => RepairAssetTypeBlocks(asset, _httpClient, token), - FileType.Audio => RepairOrPatchTypeAudio(asset, _httpClient, token), - FileType.Video => RepairAssetTypeVideo(asset, _httpClient, token), - _ => RepairAssetTypeGeneric(asset, _httpClient, token) + FileType.Blocks => RepairAssetTypeBlocks(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + FileType.Audio => RepairOrPatchTypeAudio(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + FileType.Video => RepairAssetTypeVideo(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken), + _ => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken) }; // Await the task await assetTask; - } - } - - return true; + }); } - finally + else { - // Unassign downloader event - _httpClient.DownloadProgress -= _httpClient_RepairAssetProgress; + foreach ((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset in + PairEnumeratePropertyAndAssetIndexPackage( +#if ENABLEHTTPREPAIR + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) +#else + repairAssetIndex +#endif + , assetProperty)) + { + // Assign a task depends on the asset type + Task assetTask = asset.AssetIndex.FT switch + { + FileType.Blocks => RepairAssetTypeBlocks(asset, downloadClient, _httpClient_RepairAssetProgress, token), + FileType.Audio => RepairOrPatchTypeAudio(asset, downloadClient, _httpClient_RepairAssetProgress, token), + FileType.Video => RepairAssetTypeVideo(asset, downloadClient, _httpClient_RepairAssetProgress, token), + _ => RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token) + }; + + // Await the task + await assetTask; + } } + + return true; } #region VideoRepair - private async Task RepairAssetTypeVideo((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) => await RepairAssetTypeGeneric(asset, _httpClient, token, asset.AssetIndex.RN); + private async Task RepairAssetTypeVideo((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) => + await RepairAssetTypeGeneric(asset, downloadClient, downloadProgress, token, asset.AssetIndex.RN); #endregion #region AudioRepairOrPatch - private async Task RepairOrPatchTypeAudio((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairOrPatchTypeAudio((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { if (asset.AssetIndex.IsPatchApplicable) { - await RepairTypeAudioActionPatching(asset, _httpClient, token); + await RepairTypeAudioActionPatching(asset, downloadClient, downloadProgress, token); } else { string audioURL = ConverterTool.CombineURLFromString(string.Format(_audioBaseRemotePath, $"{_gameVersion.Major}_{_gameVersion.Minor}", _gameServer.Manifest.ManifestAudio.ManifestAudioRevision), asset.AssetIndex.RN); - await RepairAssetTypeGeneric(asset, _httpClient, token, audioURL); + await RepairAssetTypeGeneric(asset, downloadClient, downloadProgress, token, audioURL); } } - private async Task RepairTypeAudioActionPatching((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairTypeAudioActionPatching((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Increment total count current _progressAllCountCurrent++; @@ -145,7 +134,7 @@ private async Task RepairTypeAudioActionPatching((FilePropertiesRemote AssetInde true); // Run patching task - await RunPatchTask(_httpClient, token, asset.AssetIndex.AudioPatchInfo.Value.PatchFileSize, asset.AssetIndex.AudioPatchInfo.Value.PatchMD5Array, + await RunPatchTask(downloadClient, downloadProgress, token, asset.AssetIndex.AudioPatchInfo.Value.PatchFileSize, asset.AssetIndex.AudioPatchInfo.Value.PatchMD5Array, patchURL, patchPath, inputFilePath, outputFilePath, true); LogWriteLine($"File [T: {asset.AssetIndex.FT}] {asset.AssetIndex.N} has been updated!", LogType.Default, true); @@ -156,7 +145,7 @@ await RunPatchTask(_httpClient, token, asset.AssetIndex.AudioPatchInfo.Value.Pat #endregion #region GenericRepair - private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token, string customURL = null) + private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, string customURL = null) { // Increment total count current _progressAllCountCurrent++; @@ -179,7 +168,7 @@ private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAss else { // Start asset download task - await RunDownloadTask(asset.AssetIndex.S, assetPath, assetURL, _httpClient, token); + await RunDownloadTask(asset.AssetIndex.S, assetPath, assetURL, downloadClient, downloadProgress, token); LogWriteLine($"File [T: {asset.AssetIndex.FT}] {(asset.AssetIndex.FT == FileType.Blocks ? asset.AssetIndex.CRC : asset.AssetIndex.N)} has been downloaded!", LogType.Default, true); } @@ -207,7 +196,7 @@ private void RemoveUnusedAssetTypeGeneric(string filePath) #endregion #region BlocksRepair - private async Task RepairAssetTypeBlocks((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairAssetTypeBlocks((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // If patching is applicable, do patching if (asset.AssetIndex.IsPatchApplicable) @@ -216,17 +205,17 @@ private async Task RepairAssetTypeBlocks((FilePropertiesRemote AssetIndex, IAsse _progressAllCountCurrent++; // Do patching - await RepairTypeBlocksActionPatching(asset, _httpClient, token); + await RepairTypeBlocksActionPatching(asset, downloadClient, downloadProgress, token); return; } // Initialize URL of the block, then run repair generic task string blockURL = asset.AssetIndex.RN; - await RepairAssetTypeGeneric(asset, _httpClient, token, blockURL); + await RepairAssetTypeGeneric(asset, downloadClient, downloadProgress, token, blockURL); } - private async Task RepairTypeBlocksActionPatching((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairTypeBlocksActionPatching((FilePropertiesRemote AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Declare variables for patch file and URL and new file path string patchURL = ConverterTool.CombineURLFromString(string.Format(_blockPatchDiffBaseURL, asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].OldVersionDir), asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].PatchHashStr + ".wmv"); @@ -241,7 +230,7 @@ private async Task RepairTypeBlocksActionPatching((FilePropertiesRemote AssetInd true); // Run patching task - await RunPatchTask(_httpClient, token, asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].PatchSize, asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].PatchHash, + await RunPatchTask(downloadClient, downloadProgress, token, asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].PatchSize, asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].PatchHash, patchURL, patchPath, inputFilePath, outputFilePath); LogWriteLine($"File [T: {asset.AssetIndex.FT}] {asset.AssetIndex.BlockPatchInfo.Value.PatchPairs[0].OldHashStr} has been updated with new block {asset.AssetIndex.BlockPatchInfo.Value.NewBlockName}!", LogType.Default, true); diff --git a/Hi3Helper.Http b/Hi3Helper.Http index f027e9ac2..98c331547 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit f027e9ac29ef05f8a22aeb6392950b98bd7fa698 +Subproject commit 98c3315477e9835f1c866bf7d7e6783045a15474 From 37e50a4cc5fad0e4c1f536ec96f31a0b77476352 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 00:43:53 +0700 Subject: [PATCH 05/25] Use ``RunDownloadTask()`` on Star Rail Cache Update --- .../CachesManagement/StarRail/Update.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index cc4da3dd1..44198700f 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -88,25 +88,10 @@ private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetPro _status.ActivityStatus = string.Format(Lang._Misc.Downloading + " {0}: {1}", asset.AssetIndex.AssetType, Path.GetFileName(asset.AssetIndex.LocalName)); UpdateAll(); - // Assign and check the path of the asset directory - string assetDir = Path.GetDirectoryName(asset.AssetIndex.LocalName); - if (!Directory.Exists(assetDir)) - { - Directory.CreateDirectory(assetDir); - } - - // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) - await downloadClient.DownloadAsync( - asset.AssetIndex.RemoteURL, - asset.AssetIndex.LocalName, - true, - progressDelegateAsync: downloadProgress, - cancelToken: token - ); - + // Run download task + await RunDownloadTask(asset.AssetIndex.Size, asset.AssetIndex.LocalName, asset.AssetIndex.RemoteURL, downloadClient, downloadProgress, token); LogWriteLine($"Downloaded cache [T: {asset.AssetIndex.AssetType}]: {Path.GetFileName(asset.AssetIndex.LocalName)}", LogType.Default, true); - // Remove Asset Entry display PopRepairAssetEntry(asset.AssetProperty); } From 426fdcaa5c2e32c58187e371696b66c058e17a44 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 00:59:50 +0700 Subject: [PATCH 06/25] Fix early disposal of "Pustaka Senadina"-type file --- CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 00af8f0a1..8deeaa8fb 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -205,7 +205,7 @@ await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentif } SenadinaFileIdentifier identifier = dict[origFileRelativePath]; - using Stream networkStream = (await HttpResponseInputStream.CreateStreamAsync(client, fileUrl, null, null, null, null, null, token))!; + Stream networkStream = (await HttpResponseInputStream.CreateStreamAsync(client, fileUrl, null, null, null, null, null, token))!; await ThrowIfFileIsNotSenadina(networkStream, token); identifier.fileStream = SenadinaFileIdentifier.CreateKangBakso(networkStream, identifier.lastIdentifier!, origFileRelativePath, (int)identifier.fileTime); From 6314afd05384b95a1da34f0a813d36489e0cbf63 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 23 Aug 2024 21:57:01 +0700 Subject: [PATCH 07/25] Always ensure the file directory existence --- .../Classes/CachesManagement/Honkai/Update.cs | 7 --- .../BaseClass/InstallManagerBase.cs | 31 ++----------- .../Genshin/GenshinInstall.cs | 8 +--- .../Classes/Interfaces/Class/ProgressBase.cs | 46 +++++++++++++------ .../Classes/RepairManagement/Genshin/Check.cs | 4 +- .../Classes/RepairManagement/Honkai/Fetch.cs | 9 +--- .../RepairManagement/StarRail/Fetch.cs | 4 +- 7 files changed, 41 insertions(+), 68 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index 93d2536cc..94e8db75d 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -124,13 +124,6 @@ private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty Asset // Other than unused file, do this action else { - // Assign and check the path of the asset directory - string assetDir = Path.GetDirectoryName(asset.AssetIndex.ConcatPath); - if (!Directory.Exists(assetDir)) - { - Directory.CreateDirectory(assetDir!); - } - #if DEBUG LogWriteLine($"Downloading cache [T: {asset.AssetIndex.DataType}]: {asset.AssetIndex.N} at URL: {asset.AssetIndex.ConcatURL}", LogType.Debug, true); #endif diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 1e7862352..01aab526c 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -1635,13 +1635,7 @@ private async Task ExtractUsingNativeZipWorker(IEnumerable entriesIndex, L continue; } - string outputPath = Path.Combine(_gamePath, zipEntry.Key); - string dirPath = Path.GetDirectoryName(outputPath); - - if (!Directory.Exists(dirPath) && dirPath != null) - { - Directory.CreateDirectory(dirPath); - } + string outputPath = EnsureCreationOfDirectory(Path.Combine(_gamePath, zipEntry.Key)); int read; await using FileStream outputStream = @@ -3420,15 +3414,7 @@ private void MoveFileToIngredientList(List assetIndex, str { // Get the combined path from the asset name var inputPath = Path.Combine(sourcePath, index.N); - var outputPath = Path.Combine(targetPath, index.N); - var outputFolder = Path.GetDirectoryName(outputPath); - - // Create directory of the output path if not exist - if (!Directory.Exists(outputFolder) - && outputFolder != null) - { - Directory.CreateDirectory(outputFolder); - } + var outputPath = EnsureCreationOfDirectory(Path.Combine(targetPath, index.N)); // Sanity Check: If the file is still missing even after the process, then throw var fileInfo = new FileInfo(inputPath); @@ -3542,15 +3528,6 @@ private async ValueTask RunPackageDownloadRoutine(Http httpClient, _progressAllCountTotal)}"; LogWriteLine($"Downloading package URL {_progressAllCountCurrent}/{_progressAllCountTotal} ({ConverterTool.SummarizeSizeSimple(package.Size)}): {package.URL}"); - // Get the directory path - string pathDir = Path.GetDirectoryName(package.PathOutput); - - // If the directory doesn't exist, then create one - if (!Directory.Exists(pathDir) && pathDir != null) - { - Directory.CreateDirectory(pathDir); - } - // If the file exist or package size is unmatched, // then start downloading long existingPackageFileSize = package.GetStreamLength(_downloadThreadCount); @@ -3564,11 +3541,11 @@ private async ValueTask RunPackageDownloadRoutine(Http httpClient, bool isCanMultiSession = package.Size >= 10 << 20; if (isCanMultiSession) { - await httpClient.Download(package.URL, package.PathOutput, _downloadThreadCount, false, token); + await httpClient.Download(package.URL, EnsureCreationOfDirectory(package.PathOutput), _downloadThreadCount, false, token); } else { - await httpClient.Download(package.URL, package.PathOutput, false, null, null, token); + await httpClient.Download(package.URL, EnsureCreationOfDirectory(package.PathOutput), false, null, null, token); } // Update status to merging diff --git a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs index a91c9a351..684fb7b24 100644 --- a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs @@ -141,13 +141,7 @@ protected void EnsureMoveOldToNewAudioDirectory() foreach (string oldPath in Directory.EnumerateFiles(_gameAudioOldPath, "*", SearchOption.AllDirectories)) { string basePath = oldPath.AsSpan()[offset..].ToString(); - string newPath = Path.Combine(_gameAudioNewPath, basePath); - string newFolder = Path.GetDirectoryName(newPath); - - if (!Directory.Exists(newFolder) && newFolder != null) - { - Directory.CreateDirectory(newFolder); - } + string newPath = EnsureCreationOfDirectory(Path.Combine(_gameAudioNewPath, basePath)); FileInfo oldFileInfo = new FileInfo(oldPath) { diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 37eab57c6..53a339611 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -540,6 +540,9 @@ protected async Task DoCopyStreamProgress(Stream source, Stream target, Cancella protected string EnsureCreationOfDirectory(string str) { + if (string.IsNullOrEmpty(str)) + return str; + string dir = Path.GetDirectoryName(str); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir!); @@ -950,21 +953,38 @@ protected bool SummarizeStatusAndProgress(List assetIndex, string msgIfFound protected virtual bool IsArrayMatch(ReadOnlySpan source, ReadOnlySpan target) => source.SequenceEqual(target); - protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) + protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, + DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, bool isOverwrite = true) { - // Check for directory availability - string dirPath = Path.GetDirectoryName(assetPath); - if (!Directory.Exists(dirPath)) - Directory.CreateDirectory(dirPath); - // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) - await downloadClient.DownloadAsync( - assetURL, - assetPath, - true, - progressDelegateAsync: downloadProgress, - cancelToken: token - ); + if (assetSize < 10 << 10) + { + using FileStream fileStream = File.Open( + EnsureCreationOfDirectory(assetPath), + isOverwrite ? + FileMode.Create : + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.ReadWrite); + + await downloadClient.DownloadAsync( + assetURL, + fileStream, + !isOverwrite, + progressDelegateAsync: downloadProgress, + cancelToken: token + ); + } + else + { + await downloadClient.DownloadAsync( + assetURL, + EnsureCreationOfDirectory(assetPath), + isOverwrite, + progressDelegateAsync: downloadProgress, + cancelToken: token + ); + } } protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, Http _httpClient, CancellationToken token) diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs index ecb4c7ee2..1708f2ca9 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs @@ -116,11 +116,9 @@ private void TryMoveAudioPersistent(IEnumerable assetIndex // Trim the path name to get the generic languageName/filename form string pathName = filePath.AsSpan().Slice(audioPersistentPath.Length + 1).ToString(); // Combine the generic name with audioAsbPath - string newPath = Path.Combine(audioAsbPath, pathName); - string newPathDir = Path.GetDirectoryName(newPath); + string newPath = EnsureCreationOfDirectory(Path.Combine(audioAsbPath, pathName)); // Try move the file to the asb path - if (!Directory.Exists(newPathDir)) Directory.CreateDirectory(newPathDir); File.Move(filePath, newPath, true); } } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 8deeaa8fb..211901391 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -513,15 +513,8 @@ private async ValueTask IsAudioFileAvailable(ManifestAssetInfo audioInfo, // ReSharper disable once UnusedParameter.Local private async Task TryGetAudioManifest(HttpClient client, SenadinaFileIdentifier senadinaFileIdentifier, string manifestLocal, string manifestRemote, CancellationToken token) { - // Always check if the folder is exist - string manifestFolder = Path.GetDirectoryName(manifestLocal); - if (!Directory.Exists(manifestFolder)) - { - Directory.CreateDirectory(manifestFolder!); - } - using Stream originalFile = await senadinaFileIdentifier!.GetOriginalFileStream(client!, token); - using FileStream localFile = new FileStream(manifestLocal!, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); + using FileStream localFile = new FileStream(EnsureCreationOfDirectory(manifestLocal!), FileMode.Create, FileAccess.Write, FileShare.ReadWrite); // Start downloading manifest.m await DoCopyStreamProgress(originalFile, localFile, token); diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index 8632c0324..e65996ad3 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -352,10 +352,8 @@ private void ConvertSRMetadataToAssetIndex(SRMetadataBase metadata, List Date: Sat, 24 Aug 2024 14:15:49 +0700 Subject: [PATCH 08/25] Moving Legacy ``Http()`` to ``DownloadClient()`` (phase 4) + Launcher update mechanism + FallbackCDNUtil APIs + Game conversion mechanism --- .../InstallManagerBase.PkgVersion.cs | 11 +- .../GameConversionManagement.cs | 56 +++---- .../RegionManagement/FallbackCDNUtil.cs | 145 +++++++++++++++++- .../Pages/Dialogs/InstallationConvert.xaml.cs | 30 ++-- .../XAMLs/Updater/Classes/Updater.cs | 44 +++--- .../XAMLs/Updater/UpdaterWindow.xaml.cs | 39 +++-- Hi3Helper.Http | 2 +- 7 files changed, 239 insertions(+), 88 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 715a9c23b..5547f033e 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -4,6 +4,7 @@ using CollapseLauncher.Pages; using Hi3Helper; using Hi3Helper.Data; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; @@ -178,7 +179,7 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc Locale.Lang._FileCleanupPage.LoadingTitle, Locale.Lang._FileCleanupPage.LoadingSubtitle2); - using Http client = new Http(httpClient); + DownloadClient downloadClient = DownloadClient.CreateInstance(httpClient); RegionResourceVersion? packageLatestBase = _gameVersionManager .GetGameLatestZip(gameStateEnum).FirstOrDefault(); string? packageExtractBasePath = packageLatestBase?.decompressed_path; @@ -190,7 +191,7 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc // Check Fail-safe: Download main pkg_version file string mainPkgVersionUrl = ConverterTool.CombineURLFromString(packageExtractBasePath, "pkg_version"); - await client.Download(mainPkgVersionUrl, pkgVersionPath, true); + await downloadClient.DownloadAsync(mainPkgVersionUrl, pkgVersionPath, true); // Check Fail-safe: Download audio pkg_version files if (!string.IsNullOrEmpty(_gameAudioLangListPathStatic) && @@ -205,7 +206,7 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, packageExtractBasePath, - client); + downloadClient); } } @@ -300,7 +301,7 @@ await Task.Run(() => } protected virtual async ValueTask DownloadOtherAudioPkgVersion(string audioListFilePath, string baseExtractUrl, - Http client) + DownloadClient downloadClient) { // Initialize reader using StreamReader reader = new StreamReader(audioListFilePath); @@ -326,7 +327,7 @@ protected virtual async ValueTask DownloadOtherAudioPkgVersion(string audioListF } // Download the file - await client.Download(pkgUrl, pkgPath, true); + await downloadClient.DownloadAsync(pkgUrl, pkgPath, true); } } diff --git a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs index 993b773ab..69e7304f4 100644 --- a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs +++ b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs @@ -1,6 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Metadata; using Hi3Helper; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Preset; using Hi3Helper.Shared.ClassStruct; @@ -29,7 +30,6 @@ public class GameConversionManagement : IDisposable private PresetConfig SourceProfile, TargetProfile; private List SourceFileManifest; private List TargetFileManifest; - private Http _http; private HttpClient _client; string BaseURL; @@ -49,7 +49,6 @@ internal GameConversionManagement(PresetConfig SourceProfile, PresetConfig Targe .UseLauncherConfig() .SetAllowedDecompression(DecompressionMethods.None) .Create(); - this._http = new Http(this._client); this.SourceProfile = SourceProfile; this.TargetProfile = TargetProfile; this.BaseURL = BaseURL; @@ -64,7 +63,6 @@ internal GameConversionManagement(PresetConfig SourceProfile, PresetConfig Targe public void Dispose() { this._client?.Dispose(); - this._http?.Dispose(); } public async Task StartPreparation() @@ -77,6 +75,8 @@ public async Task StartPreparation() string IngredientsPath = TargetProfile.ActualGameDataLocation + "_Ingredients"; string URL = ""; + DownloadClient downloadClient = DownloadClient.CreateInstance(_client); + try { FallbackCDNUtil.DownloadProgress += FetchIngredientsAPI_Progress; @@ -85,7 +85,7 @@ public async Task StartPreparation() { URL = string.Format(AppGameRepairIndexURLPrefix, SourceProfile.ProfileName, this.GameVersion); ConvertDetail = Lang._InstallConvert.Step2Subtitle; - await FallbackCDNUtil.DownloadCDNFallbackContent(_http, buffer, URL, Token); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, buffer, URL, Token); buffer.Position = 0; SourceFileRemote = await buffer.DeserializeAsync>(CoreLibraryJSONContext.Default, Token); } @@ -94,7 +94,7 @@ public async Task StartPreparation() { URL = string.Format(AppGameRepairIndexURLPrefix, TargetProfile.ProfileName, this.GameVersion); ConvertDetail = Lang._InstallConvert.Step2Subtitle; - await FallbackCDNUtil.DownloadCDNFallbackContent(_http, buffer, URL, Token); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, buffer, URL, Token); buffer.Position = 0; TargetFileRemote = await buffer.DeserializeAsync>(CoreLibraryJSONContext.Default, Token); } @@ -107,7 +107,7 @@ public async Task StartPreparation() SourceFileManifest = BuildManifest(SourceFileRemote); TargetFileManifest = BuildManifest(TargetFileRemote); await Task.Run(() => PrepareIngredients(SourceFileManifest)); - await RepairIngredients(await VerifyIngredients(SourceFileManifest, IngredientsPath), IngredientsPath); + await RepairIngredients(downloadClient, await VerifyIngredients(SourceFileManifest, IngredientsPath), IngredientsPath); } long MakeIngredientsRead = 0; @@ -148,8 +148,10 @@ private void PrepareIngredients(List FileManifest) public async Task PostConversionVerify() { + DownloadClient downloadClient = DownloadClient.CreateInstance(_client); + string TargetPath = TargetProfile.ActualGameDataLocation; - await RepairIngredients(await VerifyIngredients(TargetFileManifest, TargetPath), TargetPath); + await RepairIngredients(downloadClient, await VerifyIngredients(TargetFileManifest, TargetPath), TargetPath); } private async Task> VerifyIngredients(List FileManifest, string GamePath) @@ -245,45 +247,37 @@ private List BuildBlockManifest(List BlockC, strin long RepairRead = 0; long RepairTotalSize = 0; - private async Task RepairIngredients(List BrokenFile, string GamePath) + private async Task RepairIngredients(DownloadClient downloadClient, List BrokenFile, string GamePath) { if (BrokenFile.Count == 0) return; ResetSw(); - string OutputPath; - string InputURL; RepairTotalSize = BrokenFile.Sum(x => x.FileSize); ConvertStatus = Lang._InstallConvert.Step3Title1; - foreach (FileProperties Entry in BrokenFile) + await Parallel.ForEachAsync(BrokenFile, new ParallelOptions + { + MaxDegreeOfParallelism = DownloadThread, + CancellationToken = Token + }, async (Entry, CoopToken) => { Token.ThrowIfCancellationRequested(); - OutputPath = Path.Combine(GamePath, Entry.FileName); - InputURL = CombineURLFromString(BaseURL, Entry.FileName); - ConvertDetail = string.Format("{0}: {1}", Lang._Misc.Downloading, string.Format(Lang._Misc.PerFromTo, Entry.FileName, Entry.FileSizeStr)); - if (!Directory.Exists(Path.GetDirectoryName(OutputPath))) - Directory.CreateDirectory(Path.GetDirectoryName(OutputPath)); + string OutputPath = Path.Combine(GamePath, Entry.FileName); + string OutputPathDir = Path.GetDirectoryName(OutputPath); + string InputURL = CombineURLFromString(BaseURL, Entry.FileName); - if (File.Exists(OutputPath)) - File.Delete(OutputPath); + ConvertDetail = string.Format("{0}: {1}", Lang._Misc.Downloading, string.Format(Lang._Misc.PerFromTo, Entry.FileName, Entry.FileSizeStr)); + if (!Directory.Exists(OutputPathDir)) + Directory.CreateDirectory(OutputPathDir); - _http.DownloadProgress += RepairIngredients_Progress; - if (Entry.FileSize >= 20 << 20) - { - await _http.Download(InputURL, OutputPath, DownloadThread, true, Token); - await _http.Merge(Token); - } - else - await _http.Download(InputURL, new FileStream(OutputPath, FileMode.Create, FileAccess.Write), null, null, Token); - _http.DownloadProgress -= RepairIngredients_Progress; - } + await downloadClient.DownloadAsync(InputURL, OutputPath, true, progressDelegateAsync: RepairIngredients_Progress, maxConnectionSessions: DownloadThread, cancelToken: CoopToken); + }); } - private void RepairIngredients_Progress(object sender, DownloadEvent e) + private void RepairIngredients_Progress(int read, DownloadProgress downloadProgress) { - if (_http.DownloadState != DownloadState.Merging) - RepairRead += e.Read; + Interlocked.Add(ref RepairRead, read); UpdateProgress(RepairRead, RepairTotalSize, 1, 1, ConvertSw.Elapsed, ConvertStatus, ConvertDetail); diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index 91ac230e2..7c474d1f9 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -1,6 +1,7 @@ using CollapseLauncher.Helper; using Hi3Helper; using Hi3Helper.Data; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.Region; using Squirrel.Sources; @@ -30,12 +31,12 @@ public async Task DownloadFile(string url, string targetFile, Action progre .SetAllowedDecompression(DecompressionMethods.None) .Create(); - using Http _httpClient = new Http(true, customHttpClient: client); + DownloadClient downloadClient = DownloadClient.CreateInstance(client); EventHandler progressEvent = (_, b) => progress((int)b.ProgressPercentage); try { FallbackCDNUtil.DownloadProgress += progressEvent; - await FallbackCDNUtil.DownloadCDNFallbackContent(_httpClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), default); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), default); } catch { throw; } finally @@ -160,6 +161,34 @@ public static async Task DownloadCDNFallbackContent(Http httpInstance, string ou } } + public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, string outputPath, int parallelThread, string relativeURL, CancellationToken token) + { + // Get the preferred CDN first and try get the content + CDNURLProperty preferredCDN = GetPreferredCDN(); + bool isSuccess = await TryGetCDNContent(preferredCDN, downloadClient, outputPath, relativeURL, parallelThread, token); + + // If successful, then return + if (isSuccess) return; + + // If the fail return code occurred by the token, then throw cancellation exception + token.ThrowIfCancellationRequested(); + + // If not, then continue to get the content from another CDN + foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) + { + isSuccess = await TryGetCDNContent(fallbackCDN, downloadClient, outputPath, relativeURL, parallelThread, token); + + // If successful, then return + if (isSuccess) return; + } + + // If all of them failed, then throw an exception + if (!isSuccess) + { + throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); + } + } + public static async Task DownloadCDNFallbackContent(Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token) { // Argument check @@ -191,6 +220,37 @@ public static async Task DownloadCDNFallbackContent(Http httpInstance, Stream ou } } + public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) + { + // Argument check + PerformStreamCheckAndSeek(outputStream); + + // Get the preferred CDN first and try get the content + CDNURLProperty preferredCDN = GetPreferredCDN(); + bool isSuccess = await TryGetCDNContent(preferredCDN, downloadClient, outputStream, relativeURL, token); + + // If successful, then return + if (isSuccess) return; + + // If the fail return code occurred by the token, then throw cancellation exception + token.ThrowIfCancellationRequested(); + + // If not, then continue to get the content from another CDN + foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) + { + isSuccess = await TryGetCDNContent(fallbackCDN, downloadClient, outputStream, relativeURL, token); + + // If successful, then return + if (isSuccess) return; + } + + // If all of them failed, then throw an exception + if (!isSuccess) + { + throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); + } + } + public static async ValueTask TryGetCDNFallbackStream(string relativeURL, CancellationToken token = default, bool isForceUncompressRequest = false) { // Get the preferred CDN first and try get the content @@ -318,6 +378,57 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Ht } } + private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) + { + try + { + // Get the URL Status then return boolean and and URLStatus + (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); + + // If URL status is false, then return false + if (!urlStatus.Item1) return false; + + // Continue to get the content and return true if successful + await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, token); + return true; + } + // Handle the error and log it. If fails, then log it and return false + catch (Exception ex) + { + LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); + return false; + } + } + + private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, string outputPath, string relativeURL, int parallelThread, CancellationToken token) + { + try + { + // Get the URL Status then return boolean and and URLStatus + (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); + + // If URL status is false, then return false + if (!urlStatus.Item1) return false; + + // Continue to get the content and return true if successful + if (!cdnProp.PartialDownloadSupport) + { + // If the CDN marked to not supporting the partial download, then use single thread mode download. + using FileStream stream = File.Create(outputPath); + await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, token); + return true; + } + await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstance_DownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); + return true; + } + // Handle the error and log it. If fails, then log it and return false + catch (Exception ex) + { + LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); + return false; + } + } + private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, Http httpInstance, string relativeURL, CancellationToken token) { // Concat the URL Prefix and Relative URL @@ -339,6 +450,27 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Ht return (true, absoluteURL); } + private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token) + { + // Concat the URL Prefix and Relative URL + string absoluteURL = ConverterTool.CombineURLFromString(cdnProp.URLPrefix, relativeURL); + + LogWriteLine($"Getting CDN Content from: {cdnProp.Name} at URL: {absoluteURL}", LogType.Default, true); + + // Try check the status of the URL + (HttpStatusCode, bool) returnCode = await downloadClient.GetURLStatus(absoluteURL, token); + + // If it's not a successful code, then return false + if (!returnCode.Item2) + { + LogWriteLine($"CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL}) has returned error code: {returnCode.Item1} ({(int)returnCode.Item1})", LogType.Error, true); + return (false, absoluteURL); + } + + // Otherwise, return true + return (true, absoluteURL); + } + private static async ValueTask TryGetURLStatus(CDNURLProperty cdnProp, string relativeURL, CancellationToken token, bool isUncompressRequest) { try @@ -483,5 +615,14 @@ public static string TryGetAbsoluteToRelativeCDNURL(string URL, string searchInd // Re-send the events to the static DownloadProgress private static void HttpInstance_DownloadProgressAdapter(object sender, DownloadEvent e) => DownloadProgress?.Invoke(sender, e); + + private static DownloadEvent DownloadClientAdapter = new DownloadEvent(); + + private static void HttpInstance_DownloadProgressAdapter(int read, DownloadProgress downloadProgress) + { + DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; + DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; + DownloadClientAdapter.Read = read; + } } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs index ada8c5008..55ad21d68 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs @@ -6,12 +6,14 @@ using CollapseLauncher.Statics; using Hi3Helper; using Hi3Helper.Data; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -37,6 +39,7 @@ public partial class InstallationConvert : Page IniFile SourceIniFile; CancellationTokenSource tokenSource = new CancellationTokenSource(); private GamePresetProperty CurrentGameProperty { get; set; } + private Stopwatch CurrentStopwatch = Stopwatch.StartNew(); public InstallationConvert() { @@ -156,7 +159,8 @@ private async Task FetchDataIntegrityURL(PresetConfig Profile) .SetAllowedDecompression(DecompressionMethods.None) .Create(); - Http _Http = new Http(client); + // Use the new DownloadClient instance + DownloadClient downloadClient = DownloadClient.CreateInstance(client); Dictionary _RepoList; try @@ -165,7 +169,7 @@ private async Task FetchDataIntegrityURL(PresetConfig Profile) using (MemoryStream s = new MemoryStream()) { string repoListURL = string.Format(AppGameRepoIndexURLPrefix, Profile.ProfileName); - await FallbackCDNUtil.DownloadCDNFallbackContent(_Http, s, repoListURL, tokenSource.Token); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, s, repoListURL, tokenSource.Token); s.Position = 0; _RepoList = await s.DeserializeAsync>(CoreLibraryJSONContext.Default, tokenSource.Token); } @@ -177,13 +181,7 @@ private async Task FetchDataIntegrityURL(PresetConfig Profile) RegionResourceProp _Entry; - using (MemoryStream s = new MemoryStream()) - { - await _Http.Download(Profile.LauncherResourceURL, s, null, null, tokenSource.Token); - s.Position = 0; - _Entry = await s.DeserializeAsync(InternalAppJSONContext.Default, tokenSource.Token); - } - + _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default, tokenSource.Token); GameVersion = _Entry.data.game.latest.version; return _RepoList[GameVersion ?? throw new InvalidOperationException()]; @@ -332,13 +330,25 @@ private async Task DoDownloadRecipe() private void Step2ProgressEvents(object sender, DownloadEvent e) { + double speed = e.SizeDownloaded / CurrentStopwatch.Elapsed.TotalSeconds; DispatcherQueue?.TryEnqueue(() => { - Step2ProgressStatus.Text = $"{e.ProgressPercentage}% - {string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.Speed))}"; + Step2ProgressStatus.Text = $"{e.ProgressPercentage}% - {string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(speed))}"; Step2ProgressRing.Value = e.ProgressPercentage; }); } + private void Step2ProgressEvents(int read, DownloadProgress downloadProgress) + { + double speed = downloadProgress.BytesDownloaded / CurrentStopwatch.Elapsed.TotalSeconds; + double percentage = GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal); + DispatcherQueue?.TryEnqueue(() => + { + Step2ProgressStatus.Text = $"{percentage}% - {string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(speed))}"; + Step2ProgressRing.Value = percentage; + }); + } + private async Task DoPrepareIngredients() { DispatcherQueue?.TryEnqueue(() => diff --git a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs index b2e79a8b7..91cbbd0d7 100644 --- a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs +++ b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs @@ -1,6 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Update; using Hi3Helper; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Squirrel; using Squirrel.Sources; @@ -128,7 +129,7 @@ await UpdateManager.ApplyReleases(UpdateInfo, (progress) => } catch (Exception ex) { - Logger.LogWriteLine($"Failed while running update via Squirrel. Failback to legacy method...\r\n{ex}", + Logger.LogWriteLine($"Failed while running update via Squirrel. Fallback to legacy method...\r\n{ex}", LogType.Error, true); IsUseLegacyDownload = true; await StartLegacyUpdate(); @@ -145,32 +146,29 @@ private async Task StartLegacyUpdate() .SetAllowedDecompression(DecompressionMethods.None) .Create(); - using (var _httpClient = new Http(true, customHttpClient: client)) - { - UpdateStopwatch = Stopwatch.StartNew(); + DownloadClient downloadClient = DownloadClient.CreateInstance(client); - await using (var stream = - await FallbackCDNUtil.TryGetCDNFallbackStream($"{ChannelName.ToLower()}/fileindex.json")) - { - var updateInfo = - await stream.DeserializeAsync(InternalAppJSONContext.Default); - var gameVersion = updateInfo!.Version; + UpdateStopwatch = Stopwatch.StartNew(); - if (gameVersion != null) NewVersionTag = gameVersion.Value!; - UpdateStatus(); - UpdateProgress(); - } + AppUpdateVersionProp updateInfo = await FallbackCDNUtil + .DownloadAsJSONType($"{ChannelName.ToLower()}/fileindex.json", + InternalAppJSONContext.Default, default); - FallbackCDNUtil.DownloadProgress += FallbackCDNUtil_DownloadProgress; - await FallbackCDNUtil.DownloadCDNFallbackContent(_httpClient, applyElevatedPath, - Environment.ProcessorCount > 8 - ? 8 - : Environment.ProcessorCount, - $"{ChannelName.ToLower()}/ApplyUpdate.exe", default); - FallbackCDNUtil.DownloadProgress -= FallbackCDNUtil_DownloadProgress; + GameVersion? gameVersion = updateInfo!.Version; - await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), ChannelName.ToLower()); - } + if (gameVersion.HasValue) NewVersionTag = gameVersion.Value!; + UpdateStatus(); + UpdateProgress(); + + FallbackCDNUtil.DownloadProgress += FallbackCDNUtil_DownloadProgress; + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, applyElevatedPath, + Environment.ProcessorCount > 8 + ? 8 + : Environment.ProcessorCount, + $"{ChannelName.ToLower()}/ApplyUpdate.exe", default); + FallbackCDNUtil.DownloadProgress -= FallbackCDNUtil_DownloadProgress; + + await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), ChannelName.ToLower()); } private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) diff --git a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs index e0af9ea89..c587c9d02 100644 --- a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs @@ -1,6 +1,7 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Update; using Hi3Helper; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Microsoft.UI.Xaml; using System; @@ -31,6 +32,8 @@ public sealed partial class UpdaterWindow Path.Combine(workingDir, Path.GetFileNameWithoutExtension(sourcePath) + ".Elevated.exe"); public static string launcherPath = Path.Combine(workingDir, "CollapseLauncher.exe"); + private static Stopwatch CurrentStopwatch = Stopwatch.StartNew(); + public UpdaterWindow() { InitializeComponent(); @@ -56,8 +59,8 @@ private async void StartAsyncRoutine() var newVerTagPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "CollapseLauncher", "_NewVer"); progressBar.IsIndeterminate = true; - UpdateChannelLabel.Text = m_arguments.Updater.UpdateChannel.ToString(); - ActivityStatus.Text = Lang._UpdatePage.UpdateMessage1; + UpdateChannelLabel.Text = m_arguments.Updater.UpdateChannel.ToString(); + ActivityStatus.Text = Lang._UpdatePage.UpdateMessage1; await using var metadataStream = await @@ -74,17 +77,18 @@ private async void StartAsyncRoutine() .SetAllowedDecompression(DecompressionMethods.None) .Create(); - using (var _httpClient = new Http(true, customHttpClient: client)) - { - FallbackCDNUtil.DownloadProgress += FallbackCDNUtil_DownloadProgress; - await FallbackCDNUtil.DownloadCDNFallbackContent(_httpClient, applyElevatedPath, - Environment.ProcessorCount > 8 - ? 8 - : Environment.ProcessorCount, - $"{m_arguments.Updater.UpdateChannel.ToString().ToLower()}/ApplyUpdate.exe", - default); - FallbackCDNUtil.DownloadProgress -= FallbackCDNUtil_DownloadProgress; - } + DownloadClient downloadClient = DownloadClient.CreateInstance(client); + + CurrentStopwatch.Restart(); + + FallbackCDNUtil.DownloadProgress += FallbackCDNUtil_DownloadProgress; + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, applyElevatedPath, + Environment.ProcessorCount > 8 + ? 8 + : Environment.ProcessorCount, + $"{m_arguments.Updater.UpdateChannel.ToString().ToLower()}/ApplyUpdate.exe", + default); + FallbackCDNUtil.DownloadProgress -= FallbackCDNUtil_DownloadProgress; await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), m_arguments.Updater.UpdateChannel.ToString().ToLower()); @@ -103,7 +107,7 @@ await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), { StartInfo = new ProcessStartInfo { - FileName = applyElevatedPath, + FileName = applyElevatedPath, UseShellExecute = true } }; @@ -119,6 +123,9 @@ await File.WriteAllTextAsync(Path.Combine(workingDir, "..\\", "release"), private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) { + double speed = e.SizeDownloaded / CurrentStopwatch.Elapsed.TotalSeconds; + TimeSpan timeLeft = ((e.SizeToBeDownloaded - e.SizeDownloaded) / speed).ToTimeSpanNormalized(); + DispatcherQueue?.TryEnqueue(() => { progressBar.IsIndeterminate = false; @@ -128,8 +135,8 @@ private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) $"{SummarizeSizeSimple(e.SizeDownloaded)} / {SummarizeSizeSimple(e.SizeToBeDownloaded)}"; SpeedStatus.Text = - string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.Speed)); - TimeEstimation.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.TimeLeft); + string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(speed)); + TimeEstimation.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, timeLeft); }); } diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 98c331547..e30835b25 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 98c3315477e9835f1c866bf7d7e6783045a15474 +Subproject commit e30835b2515ada1b560a60e47e4633cb6bd6e3bd From 2942aeacaf09a58e0d8d9f4c5a16f197e1de471b Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 24 Aug 2024 14:16:11 +0700 Subject: [PATCH 09/25] Moving Legacy ``Http()`` to ``DownloadClient()`` (phase 5 - GI) Genshin Impact Game Repair --- .../Classes/RepairManagement/Genshin/Fetch.cs | 128 ++++++++++-------- .../RepairManagement/Genshin/Repair.cs | 73 ++++------ 2 files changed, 104 insertions(+), 97 deletions(-) diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index 3a38f5c6b..e75712b37 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -4,7 +4,7 @@ using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.EncTool.Parser.AssetIndex; -using Hi3Helper.Http.Legacy; +using Hi3Helper.Http; using Hi3Helper.Shared.ClassStruct; using System; using System.Collections.Generic; @@ -39,39 +39,28 @@ private async ValueTask> Fetch(List assetIndex) @@ -102,7 +91,7 @@ private List EliminateUnnecessaryAssetIndex(IEnumerable assetIndex, Dictionary hashtableManifest, CancellationToken token) + private async Task BuildPrimaryManifest(DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, List assetIndex, Dictionary hashtableManifest, CancellationToken token) { try { @@ -117,8 +106,14 @@ private async Task BuildPrimaryManifest(Http _httpClient, List BuildPersistentManifest(Http _httpClient, List assetIndex, + private async Task BuildPersistentManifest(DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, List assetIndex, Dictionary hashtableManifest, CancellationToken token) { try { // Get the Dispatcher Query - QueryProperty queryProperty = await GetDispatcherQuery(_httpClient, token); + QueryProperty queryProperty = await GetDispatcherQuery(token); // Initialize persistent folder path and check for the folder existence string basePersistentPath = $"{_execPrefix}_Data\\Persistent"; @@ -226,7 +245,7 @@ private async Task BuildPersistentManifest(Http _httpClient, List assetIndex, Dictionary hashtable, @@ -279,7 +298,7 @@ private async ValueTask ParseManifestToAssetIndex(Http _httpClient, string prima } // Download the manifest - await _httpClient.Download(manifestURL, manifestPath, true, null, null, token); + await downloadClient.DownloadAsync(manifestURL, manifestPath, true, progressDelegateAsync: downloadProgress, cancelToken: token); LogWriteLine($"Manifest: {manifestRemoteName} (localName: {manifestLocalName}) has been fetched", LogType.Default, true); // Parse the manifest @@ -429,7 +448,7 @@ private void SavePersistentRevision(QueryProperty dispatchQuery) #region DispatcherParser // ReSharper disable once UnusedParameter.Local - private async Task GetDispatcherQuery(Http _httpClient, CancellationToken token) + private async Task GetDispatcherQuery(CancellationToken token) { // Initialize dispatch helper using (GenshinDispatchHelper dispatchHelper = new GenshinDispatchHelper(_dispatcherRegionID, _gameVersionManager.GamePreset.ProtoDispatchKey, _dispatcherURL, _gameVersion.VersionString, token)) @@ -531,14 +550,15 @@ private void ParseManifestToAssetIndex(string manifestPath, List Repair(List repairAssetIndex, Can .SetAllowedDecompression(DecompressionMethods.None) .Create(); - // Use HttpClient instance on fetching - using Http _httpClient = new Http(true, 5, 1000, _userAgent, client); + // Use the new DownloadClient instance + DownloadClient downloadClient = DownloadClient.CreateInstance(client); - // Try running instance - try + // Iterate repair asset + ObservableCollection assetProperty = new ObservableCollection(AssetEntry); + if (_isBurstDownloadEnabled) { - // Assign downloader event - _httpClient.DownloadProgress += _httpClient_RepairAssetProgress; - - // Iterate repair asset - ObservableCollection assetProperty = new ObservableCollection(AssetEntry); - if (_isBurstDownloadEnabled) - { - await Parallel.ForEachAsync( - PairEnumeratePropertyAndAssetIndexPackage( + await Parallel.ForEachAsync( + PairEnumeratePropertyAndAssetIndexPackage( #if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) #else - repairAssetIndex + repairAssetIndex #endif - , assetProperty), - new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, - async (asset, innerToken) => - { - await RepairAssetTypeGeneric(asset, _httpClient, innerToken); - }); - } - else - { - foreach ((PkgVersionProperties AssetIndex, IAssetProperty AssetProperty) asset in - PairEnumeratePropertyAndAssetIndexPackage( + , assetProperty), + new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = _downloadThreadCount }, + async (asset, innerToken) => + { + await RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, innerToken); + }); + } + else + { + foreach ((PkgVersionProperties AssetIndex, IAssetProperty AssetProperty) asset in + PairEnumeratePropertyAndAssetIndexPackage( #if ENABLEHTTPREPAIR - EnforceHTTPSchemeToAssetIndex(repairAssetIndex) + EnforceHTTPSchemeToAssetIndex(repairAssetIndex) #else - repairAssetIndex + repairAssetIndex #endif - , assetProperty)) - { - await RepairAssetTypeGeneric(asset, _httpClient, token); - } + , assetProperty)) + { + await RepairAssetTypeGeneric(asset, downloadClient, _httpClient_RepairAssetProgress, token); } - - return true; - } - finally - { - // Unassign downloader event - _httpClient.DownloadProgress -= _httpClient_RepairAssetProgress; } + + return true; } #region GenericRepair - private async Task RepairAssetTypeGeneric((PkgVersionProperties AssetIndex, IAssetProperty AssetProperty) asset, Http _httpClient, CancellationToken token) + private async Task RepairAssetTypeGeneric((PkgVersionProperties AssetIndex, IAssetProperty AssetProperty) asset, DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token) { // Increment total count current _progressAllCountCurrent++; @@ -113,7 +100,7 @@ private async Task RepairAssetTypeGeneric((PkgVersionProperties AssetIndex, IAss string assetPath = Path.Combine(_gamePath, ConverterTool.NormalizePath(asset.AssetIndex.remoteName)); // or start asset download task - await RunDownloadTask(asset.AssetIndex.fileSize, assetPath, asset.AssetIndex.remoteURL, _httpClient, token); + await RunDownloadTask(asset.AssetIndex.fileSize, assetPath, asset.AssetIndex.remoteURL, downloadClient, downloadProgress, token); LogWriteLine($"File [T: {RepairAssetType.General}] {asset.AssetIndex.remoteName} has been downloaded!", LogType.Default, true); } From 5cb2cc84f625ca5eb0edfa21500b1096a8e59ca9 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 24 Aug 2024 23:59:10 +0700 Subject: [PATCH 10/25] Add ``long`` and ``ulong`` parser on ``IniFile`` --- Hi3Helper.Core/Classes/Data/Tools/IniFile.cs | 83 +++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Hi3Helper.Core/Classes/Data/Tools/IniFile.cs b/Hi3Helper.Core/Classes/Data/Tools/IniFile.cs index 4234e4273..7154766bd 100644 --- a/Hi3Helper.Core/Classes/Data/Tools/IniFile.cs +++ b/Hi3Helper.Core/Classes/Data/Tools/IniFile.cs @@ -13,7 +13,6 @@ namespace Hi3Helper.Data { // Reference // https://github.com/Enichan/Ini - [DebuggerStepThrough] public struct IniValue { private static bool TryParseInt(string text, out int value) @@ -46,6 +45,36 @@ private static bool TryParseUInt(string text, out uint value) return false; } + private static bool TryParseLong(string text, out long value) + { + long res; + if (long.TryParse(text, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out res)) + { + value = res; + return true; + } + value = 0; + return false; + } + + private static bool TryParseUlong(string text, out ulong value) + { + ulong res; + if (ulong.TryParse(text, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out res)) + { + value = res; + return true; + } + value = 0; + return false; + } + private static bool TryParseDouble(string text, out double value) { double res; @@ -188,6 +217,26 @@ public uint ToUInt(uint valueIfInvalid = 0) return valueIfInvalid; } + public long ToLong(long valueIfInvalid = 0) + { + long res; + if (TryConvertLong(out res)) + { + return res; + } + return valueIfInvalid; + } + + public ulong ToUlong(ulong valueIfInvalid = 0) + { + ulong res; + if (TryConvertUlong(out res)) + { + return res; + } + return valueIfInvalid; + } + public bool TryConvertUInt(out uint result) { if (Value == null) @@ -216,6 +265,34 @@ public bool TryConvertInt(out int result) return false; } + public bool TryConvertLong(out long result) + { + if (Value == null) + { + result = default(long); + return false; + } + if (TryParseLong(Value.Trim(), out result)) + { + return true; + } + return false; + } + + public bool TryConvertUlong(out ulong result) + { + if (Value == null) + { + result = default(ulong); + return false; + } + if (TryParseUlong(Value.Trim(), out result)) + { + return true; + } + return false; + } + public float ToFloat(float valueIfInvalid = 0) { float res; @@ -294,12 +371,16 @@ public string GetString(bool allowOuterQuotes, bool preserveWhitespace) public static implicit operator IniValue(int o) => new IniValue(o); + public static implicit operator IniValue(long o) => new IniValue(o); + public static implicit operator IniValue(sbyte o) => new IniValue(o); public static implicit operator IniValue(ushort o) => new IniValue(o); public static implicit operator IniValue(uint o) => new IniValue(o); + public static implicit operator IniValue(ulong o) => new IniValue(o); + public static implicit operator IniValue(float o) => new IniValue(o); public static implicit operator IniValue(double o) => new IniValue(o); From 9eac564c5538998bf8895c407372de3a9a06e94a Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Sun, 25 Aug 2024 14:36:37 +0700 Subject: [PATCH 11/25] Update submodules --- Hi3Helper.EncTool | 2 +- Hi3Helper.Http | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index d3ded7209..4fdf1e971 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit d3ded72092e2076d6e4d6cb336d4c8e0589763bd +Subproject commit 4fdf1e971946421fbb1b767ed41bcaff0201b5e6 diff --git a/Hi3Helper.Http b/Hi3Helper.Http index e30835b25..70266f9e7 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit e30835b2515ada1b560a60e47e4633cb6bd6e3bd +Subproject commit 70266f9e7ca1a17171b79f29a77172227870fb17 From 29c1cc83117c5e51f35b62ac151cf72479fad610 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Sun, 25 Aug 2024 14:46:42 +0700 Subject: [PATCH 12/25] A lil trolling --- .../GameConversionManagement.cs | 2 +- .../RepairManagement/StarRail/Fetch.cs | 80 +++++-------------- CollapseLauncher/packages.lock.json | 6 +- Hi3Helper.Core/packages.lock.json | 8 +- Hi3Helper.EncTool.Test/Program.cs | 1 + Hi3Helper.Http | 2 +- InnoSetupHelper/packages.lock.json | 10 +-- 7 files changed, 35 insertions(+), 74 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs index 69e7304f4..5e598b558 100644 --- a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs +++ b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs @@ -269,7 +269,7 @@ private async Task RepairIngredients(DownloadClient downloadClient, List pkgVersion = new List(); // Initialize repo metadata - bool isSuccess = false; try { // Get the metadata Dictionary repoMetadata = await FetchMetadata(token); // Check for manifest. If it doesn't exist, then throw and warn the user - if (!(isSuccess = repoMetadata.ContainsKey(_gameVersion.VersionString))) + if (!(repoMetadata.TryGetValue(_gameVersion.VersionString, out var value))) { throw new VersionNotFoundException($"Manifest for {_gameVersionManager.GamePreset.ZoneName} (version: {_gameVersion.VersionString}) doesn't exist! Please contact @neon-nyan or open an issue for this!"); } // Assign the URL based on the version - _gameRepoURL = repoMetadata[_gameVersion.VersionString]; + _gameRepoURL = value; } // If the base._isVersionOverride is true, then throw. This sanity check is required if the delta patch is being performed. catch when (base._isVersionOverride) { throw; } - // Fetch the asset index from CDN (also check if the status is success) - if (isSuccess) - { - // Set asset index URL - string urlIndex = string.Format(LauncherConfig.AppGameRepairIndexURLPrefix, _gameVersionManager.GamePreset.ProfileName, _gameVersion.VersionString) + ".binv2"; + // Fetch the asset index from CDN + // Set asset index URL + string urlIndex = string.Format(LauncherConfig.AppGameRepairIndexURLPrefix, _gameVersionManager.GamePreset.ProfileName, _gameVersion.VersionString) + ".binv2"; - // Start downloading asset index using FallbackCDNUtil and return its stream - await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(urlIndex, token); - if (stream != null) - { - // Deserialize asset index and set it to list - AssetIndexV2 parserTool = new AssetIndexV2(); - pkgVersion = new List(parserTool.Deserialize(stream, out DateTime timestamp)); - LogWriteLine($"Asset index timestamp: {timestamp}", LogType.Default, true); - } - } - else + // Start downloading asset index using FallbackCDNUtil and return its stream + await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(urlIndex, token); + if (stream != null) { - // If the base._isVersionOverride is true (for delta patch), then return - if (base._isVersionOverride) return; - LogWriteLine($"Falling back to the miHoYo provided pkg_version as the asset index!", LogType.Warning, true); - - // Get the latest game property from the API - GameInstallStateEnum gameState = await _gameVersionManager.GetGameState(); - RegionResourceVersion gameVersion = _gameVersionManager.GetGameLatestZip(gameState).FirstOrDefault(); - - // If the gameVersion is null, then return - if (gameVersion == null) return; - - // Try get the uncompressed url and the pkg_version - string baseZipURL = gameVersion.path; - int lastIndexOfURL = baseZipURL.LastIndexOf('/'); - string baseUncompressedURL = baseZipURL.Substring(0, lastIndexOfURL); - baseUncompressedURL = CombineURLFromString(baseUncompressedURL, "unzip"); - string basePkgVersionUrl = CombineURLFromString(baseUncompressedURL, "pkg_version"); - - try - { - // Set the _gameRepoURL - _gameRepoURL = baseUncompressedURL; - - // Try get the data - using MemoryStream ms = new MemoryStream(); - using StreamReader sr = new StreamReader(ms); - await downloadClient.DownloadAsync(basePkgVersionUrl, ms, false, _httpClient_FetchAssetProgress); - - // Read the stream and deserialize the JSON - pkgVersion.Clear(); - while (!sr.EndOfStream) - { - // Deserialize and add the line to pkgVersion list - string jsonLine = sr.ReadLine(); - PkgVersionProperties entry = jsonLine.Deserialize(CoreLibraryJSONContext.Default); - pkgVersion.Add(entry); - } - } - catch { throw; } + // Deserialize asset index and set it to list + AssetIndexV2 parserTool = new AssetIndexV2(); + pkgVersion = new List(parserTool.Deserialize(stream, out DateTime timestamp)); + LogWriteLine($"Asset index timestamp: {timestamp}", LogType.Default, true); } // Convert the pkg version list to asset index @@ -274,8 +228,14 @@ private FilePropertiesRemote GetNormalizedFilePropertyTypeBased(string remoteAbs GetNormalizedFilePropertyTypeBased(remoteAbsolutePath, remoteRelativePath, fileSize, hash, type, false, isPatchApplicable, isHasHashMark); - private FilePropertiesRemote GetNormalizedFilePropertyTypeBased(string remoteParentURL, string remoteRelativePath, long fileSize, - string hash, FileType type = FileType.Generic, bool isPkgVersion = true, bool isPatchApplicable = false, bool isHasHashMark = false) + private FilePropertiesRemote GetNormalizedFilePropertyTypeBased(string remoteParentURL, + string remoteRelativePath, + long fileSize, + string hash, + FileType type = FileType.Generic, + bool isPkgVersion = true, + bool isPatchApplicable = false, + bool isHasHashMark = false) { string localAbsolutePath, remoteAbsolutePath = type switch diff --git a/CollapseLauncher/packages.lock.json b/CollapseLauncher/packages.lock.json index 46ceb7c80..6f8dcea0b 100644 --- a/CollapseLauncher/packages.lock.json +++ b/CollapseLauncher/packages.lock.json @@ -982,14 +982,14 @@ "type": "Project", "dependencies": { "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[1.5.0, )" + "Hi3Helper.Http": "[2.0.0, )" } }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.27.2, )", - "Hi3Helper.Http": "[1.5.0, )", + "Google.Protobuf": "[3.27.3, )", + "Hi3Helper.Http": "[2.0.0, )", "System.IO.Hashing": "[8.0.0, )" } }, diff --git a/Hi3Helper.Core/packages.lock.json b/Hi3Helper.Core/packages.lock.json index 44cd58e7f..3d7de3b1b 100644 --- a/Hi3Helper.Core/packages.lock.json +++ b/Hi3Helper.Core/packages.lock.json @@ -4,8 +4,8 @@ "net8.0": { "Google.Protobuf": { "type": "Transitive", - "resolved": "3.27.2", - "contentHash": "0wdgA3LO9mBS477jieBFs4pU1sWhVtwv/P+i9nAEiFDQyUA7PPHDBbJL1CeqYtV18jLiq9og4n7wSVCO171OBg==" + "resolved": "3.27.3", + "contentHash": "ld+oe1+Mc6no643d8otGm6Zylu7KBpGnAiqOW3nOpV8VxUuqhz70vRNKUU5grLaCeuUQVF8t9dm8TgmkTMr1Tg==" }, "System.IO.Hashing": { "type": "Transitive", @@ -15,8 +15,8 @@ "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.27.2, )", - "Hi3Helper.Http": "[1.5.0, )", + "Google.Protobuf": "[3.27.3, )", + "Hi3Helper.Http": "[2.0.0, )", "System.IO.Hashing": "[8.0.0, )" } }, diff --git a/Hi3Helper.EncTool.Test/Program.cs b/Hi3Helper.EncTool.Test/Program.cs index d88a27c10..2ae16e339 100644 --- a/Hi3Helper.EncTool.Test/Program.cs +++ b/Hi3Helper.EncTool.Test/Program.cs @@ -9,6 +9,7 @@ internal class Program static async Task Main(string[] args) { + return; //frick testing #region unused using (SRMetadata srm = new SRMetadata("https://globaldp-prod-os01.starrails.com/query_dispatch", "3a57430d8d", diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 70266f9e7..2dc55ec52 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 70266f9e7ca1a17171b79f29a77172227870fb17 +Subproject commit 2dc55ec52841201cbbf9d8047d6f412a89c259b9 diff --git a/InnoSetupHelper/packages.lock.json b/InnoSetupHelper/packages.lock.json index 82785ef57..460d270a1 100644 --- a/InnoSetupHelper/packages.lock.json +++ b/InnoSetupHelper/packages.lock.json @@ -10,21 +10,21 @@ }, "Google.Protobuf": { "type": "Transitive", - "resolved": "3.27.2", - "contentHash": "0wdgA3LO9mBS477jieBFs4pU1sWhVtwv/P+i9nAEiFDQyUA7PPHDBbJL1CeqYtV18jLiq9og4n7wSVCO171OBg==" + "resolved": "3.27.3", + "contentHash": "ld+oe1+Mc6no643d8otGm6Zylu7KBpGnAiqOW3nOpV8VxUuqhz70vRNKUU5grLaCeuUQVF8t9dm8TgmkTMr1Tg==" }, "hi3helper.core": { "type": "Project", "dependencies": { "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[1.5.0, )" + "Hi3Helper.Http": "[2.0.0, )" } }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.27.2, )", - "Hi3Helper.Http": "[1.5.0, )", + "Google.Protobuf": "[3.27.3, )", + "Hi3Helper.Http": "[2.0.0, )", "System.IO.Hashing": "[8.0.0, )" } }, From 3b57b28be65cacfa189ed9ba68350cf39896f782 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 25 Aug 2024 21:01:54 +0700 Subject: [PATCH 13/25] Adding settings for the new Hi3Helper.Http --- .../Classes/Extension/NumberExtensions.cs | 15 + .../Classes/Interfaces/Class/ProgressBase.cs | 55 ++-- .../RegionManagement/FallbackCDNUtil.cs | 4 +- .../XAMLs/MainApp/Pages/SettingsPage.xaml | 256 +++++++++++++++++- .../XAMLs/MainApp/Pages/SettingsPage.xaml.cs | 92 +++++++ .../XAMLs/MainApp/ValueConverters.cs | 72 ++++- .../Classes/Shared/Region/LauncherConfig.cs | 55 +++- Hi3Helper.Core/Lang/Locale/LangMisc.cs | 6 +- .../Lang/Locale/LangSettingsPage.cs | 35 +++ Hi3Helper.Core/Lang/en_US.json | 43 ++- 10 files changed, 590 insertions(+), 43 deletions(-) create mode 100644 CollapseLauncher/Classes/Extension/NumberExtensions.cs diff --git a/CollapseLauncher/Classes/Extension/NumberExtensions.cs b/CollapseLauncher/Classes/Extension/NumberExtensions.cs new file mode 100644 index 000000000..66256384d --- /dev/null +++ b/CollapseLauncher/Classes/Extension/NumberExtensions.cs @@ -0,0 +1,15 @@ +using Hi3Helper.Shared.Region; +using System; +using System.Runtime.CompilerServices; + +namespace CollapseLauncher.Extension +{ + internal static class NumberExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static double ClampLimitedSpeedNumber(this double speed) + => LauncherConfig.DownloadSpeedLimitCached > 0 ? + Math.Min(LauncherConfig.DownloadSpeedLimitCached, speed) : + speed; + } +} diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 53a339611..281af07f5 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -84,7 +84,7 @@ protected virtual async void _httpClient_FetchAssetProgress(int size, DownloadPr { if (await CheckIfNeedRefreshStopwatch()) { - double speed = downloadProgress.BytesDownloaded / _stopwatch.Elapsed.TotalSeconds; + double speed = (downloadProgress.BytesDownloaded / _stopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); TimeSpan timeLeftSpan = ((downloadProgress.BytesTotal - downloadProgress.BytesDownloaded) / speed).ToTimeSpanNormalized(); double percentage = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal, 2); @@ -144,13 +144,13 @@ protected virtual async void _httpClient_RepairAssetProgress(int size, DownloadP Interlocked.Add(ref _progressAllSizeCurrent, size); if (await CheckIfNeedRefreshStopwatch()) { - double speed = _progressAllSizeCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speed = (_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); TimeSpan timeLeftSpan = ((_progressAllSizeCurrent - _progressAllSizeTotal) / speed).ToTimeSpanNormalized(); - double percentage = ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal, 2); + double percentagePerFile = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal, 2); lock (_progress!) { - _progress.ProgressPerFilePercentage = percentage; + _progress.ProgressPerFilePercentage = percentagePerFile; _progress.ProgressPerFileSizeCurrent = downloadProgress.BytesDownloaded; _progress.ProgressPerFileSizeTotal = downloadProgress.BytesTotal; _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; @@ -258,7 +258,7 @@ protected virtual async void _httpClient_UpdateAssetProgress(int size, DownloadP if (await CheckIfNeedRefreshStopwatch()) { double speed = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; - TimeSpan timeLeftSpan = ((_progressAllSizeTotal - _progressAllSizeCurrent) / speed).ToTimeSpanNormalized(); + TimeSpan timeLeftSpan = ((_progressAllSizeTotal - _progressAllSizeCurrent) / speed.ClampLimitedSpeedNumber()).ToTimeSpanNormalized(); double percentage = ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal, 2); // Update current progress percentages and speed @@ -952,40 +952,25 @@ protected bool SummarizeStatusAndProgress(List assetIndex, string msgIfFound } protected virtual bool IsArrayMatch(ReadOnlySpan source, ReadOnlySpan target) => source.SequenceEqual(target); - + +#nullable enable protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, - DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, bool isOverwrite = true) + DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, bool isOverwrite = true, + EventHandler? downloadSpeedLimitChanged = null) { // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) - if (assetSize < 10 << 10) - { - using FileStream fileStream = File.Open( - EnsureCreationOfDirectory(assetPath), - isOverwrite ? - FileMode.Create : - FileMode.OpenOrCreate, - FileAccess.ReadWrite, - FileShare.ReadWrite); - - await downloadClient.DownloadAsync( - assetURL, - fileStream, - !isOverwrite, - progressDelegateAsync: downloadProgress, - cancelToken: token - ); - } - else - { - await downloadClient.DownloadAsync( - assetURL, - EnsureCreationOfDirectory(assetPath), - isOverwrite, - progressDelegateAsync: downloadProgress, - cancelToken: token - ); - } + await downloadClient.DownloadAsync( + assetURL, + EnsureCreationOfDirectory(assetPath), + isOverwrite, + sessionChunkSize: LauncherConfig.DownloadChunkSize, + progressDelegateAsync: downloadProgress, + cancelToken: token, + downloadSpeedLimitEvent: downloadSpeedLimitChanged, + initialDownloadSpeed: LauncherConfig.DownloadSpeedLimitCached + ); } +#nullable restore protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, Http _httpClient, CancellationToken token) { diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index 7c474d1f9..8bff6178f 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -389,7 +389,7 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do if (!urlStatus.Item1) return false; // Continue to get the content and return true if successful - await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, token); + await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); return true; } // Handle the error and log it. If fails, then log it and return false @@ -415,7 +415,7 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do { // If the CDN marked to not supporting the partial download, then use single thread mode download. using FileStream stream = File.Create(outputPath); - await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, token); + await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); return true; } await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstance_DownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml index da8c6ef45..493bc6bf1 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml @@ -9,6 +9,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ext="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" + xmlns:helperData="using:Hi3Helper.Data" xmlns:innerConfig="using:Hi3Helper.Shared.Region" xmlns:localWindowSize="using:CollapseLauncher.WindowSize" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -20,6 +21,9 @@ + + + @@ -563,6 +567,246 @@ Minimum="10" SpinButtonPlacementMode="Compact" Value="{x:Bind HttpClientTimeout, Mode=TwoWay}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -712,7 +965,8 @@ SpinButtonPlacementMode="Compact" Value="{x:Bind CurrentAppThreadExtractValue, Mode=TwoWay}" /> - diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs index 9a3b91f7c..f2cb25bc9 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs @@ -40,6 +40,7 @@ using MediaType = CollapseLauncher.Helper.Background.BackgroundMediaUtility.MediaType; using TaskSched = Microsoft.Win32.TaskScheduler.Task; using Task = System.Threading.Tasks.Task; +using Windows.Storage.Search; // ReSharper disable CheckNamespace // ReSharper disable PossibleNullReferenceException @@ -1265,7 +1266,98 @@ private string? HttpProxyPassword SetAndSaveConfigValue("HttpProxyPassword", protectedString, true); } } + + private bool IsBurstDownloadModeEnabled + { + get => LauncherConfig.IsBurstDownloadModeEnabled; + set => LauncherConfig.IsBurstDownloadModeEnabled = value; + } + + private bool IsUseDownloadSpeedLimiter + { + get + { + bool value = LauncherConfig.IsUseDownloadSpeedLimiter; + NetworkDownloadSpeedLimitGrid.Opacity = value ? 1 : 0.45; + if (value) + NetworkBurstDownloadModeToggle.IsOn = false; + NetworkBurstDownloadModeToggle.IsEnabled = !value; + return value; + } + set + { + NetworkDownloadSpeedLimitGrid.Opacity = value ? 1 : 0.45; + if (value) + NetworkBurstDownloadModeToggle.IsOn = false; + NetworkBurstDownloadModeToggle.IsEnabled = !value; + LauncherConfig.IsUseDownloadSpeedLimiter = value; + } + } + + private bool IsUsePreallocatedDownloader + { + get + { + bool value = LauncherConfig.IsUsePreallocatedDownloader; + NetworkDownloadChunkSizeGrid.Opacity = value ? 1 : 0.45; + OldDownloadChunksMergingToggle.IsEnabled = !value; + + if (!value) + { + NetworkDownloadSpeedLimitToggle.IsOn = value; + NetworkBurstDownloadModeToggle.IsOn = value; + } + NetworkDownloadSpeedLimitToggle.IsEnabled = value; + NetworkBurstDownloadModeToggle.IsEnabled = value; + return value; + } + set + { + NetworkDownloadChunkSizeGrid.Opacity = value ? 1 : 0.45; + OldDownloadChunksMergingToggle.IsEnabled = !value; + + if (!value) + { + NetworkDownloadSpeedLimitToggle.IsOn = value; + NetworkBurstDownloadModeToggle.IsOn = value; + } + NetworkDownloadSpeedLimitToggle.IsEnabled = value; + NetworkBurstDownloadModeToggle.IsEnabled = value; + LauncherConfig.IsUsePreallocatedDownloader = value; + } + } + + private double DownloadSpeedLimit + { + get + { + double val = LauncherConfig.DownloadSpeedLimit; + double valDividedM = val / (1 << 20); + return valDividedM; + } + set + { + long valBfromM = (long)(value * (1 << 20)); + + LauncherConfig.DownloadSpeedLimit = Math.Max(valBfromM, 0); + } + } + private double DownloadChunkSize + { + get + { + double val = LauncherConfig.DownloadChunkSize; + double valDividedM = val / (1 << 20); + return valDividedM; + } + set + { + int valBfromM = (int)(value * (1 << 20)); + + LauncherConfig.DownloadChunkSize = Math.Max(valBfromM, 0); + } + } #nullable restore #endregion diff --git a/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs b/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs index a8a0810d5..cc9f56a95 100644 --- a/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs +++ b/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs @@ -1,7 +1,9 @@ -using Hi3Helper.Data; +using Hi3Helper; +using Hi3Helper.Data; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data; using System; +using Windows.Globalization.NumberFormatting; namespace CollapseLauncher.Pages { @@ -56,4 +58,72 @@ public class FileSizeToStringLiteralConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string input) => ConverterTool.SummarizeSizeSimple((long)value); public object ConvertBack(object value, Type targetType, object parameter, string input) => new NotImplementedException(); } + + public class DownloadSpeedLimitToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is double asDouble) + { + long valBfromM = (long)(asDouble * (1 << 20)); + return valBfromM > 0 ? + string.Format(Locale.Lang._Misc.IsBytesMoreThanBytes, valBfromM, string.Format(Locale.Lang._Misc.SpeedPerSec, ConverterTool.SummarizeSizeSimple(valBfromM))) : + string.Format(Locale.Lang._Misc.IsBytesUnlimited); + } + return Locale.Lang._Misc.IsBytesNotANumber; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } + + public class DownloadChunkSizeToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is double asDouble) + { + int valBfromM = (int)(asDouble * (1 << 20)); + return valBfromM > 0 ? + string.Format(Locale.Lang._Misc.IsBytesMoreThanBytes, valBfromM, ConverterTool.SummarizeSizeSimple(valBfromM)) : + string.Format(Locale.Lang._Misc.IsBytesUnlimited); + } + return Locale.Lang._Misc.IsBytesNotANumber; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } + + public class BooleanToOpacityDimmConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is bool asBoolean) + return asBoolean ? 1 : 0.45; + + return 0.45; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } + + public class DoubleFormatter : INumberFormatter2, INumberParser + { + private const string Format = "{0:F5}"; + public string FormatDouble(double value) => string.Format(Format, value); + public double? ParseDouble(string text) => double.TryParse(text, out var dbl) ? dbl : null; + + public string FormatInt(long value) => throw new NotSupportedException(); + public string FormatUInt(ulong value) => throw new NotSupportedException(); + public long? ParseInt(string text) => throw new NotSupportedException(); + public ulong? ParseUInt(string text) => throw new NotSupportedException(); + } } diff --git a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs index 45c6846dd..e599385f6 100644 --- a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs +++ b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs @@ -1,4 +1,5 @@ using Hi3Helper.Data; +using Hi3Helper.Http; using Hi3Helper.Screen; using Hi3Helper.Shared.ClassStruct; using System; @@ -76,6 +77,10 @@ public static void InitAppPreset() // Assign boolean if IsConfigFileExist and IsUserHasPermission. IsFirstInstall = !(IsConfigFileExist && IsUserHasPermission); + + // Initialize the DownloadClient speed at start. + // ignored + _ = DownloadSpeedLimit; } public static bool IsConfigKeyExist(string key) => appIni.Profile![SectionName!]!.ContainsKey(key!); @@ -300,6 +305,50 @@ public static bool IsBurstDownloadModeEnabled set => SetAndSaveConfigValue("IsBurstDownloadModeEnabled", value); } + public static bool IsUsePreallocatedDownloader + { + get => GetAppConfigValue("IsUsePreallocatedDownloader").ToBoolNullable() ?? true; + set => SetAndSaveConfigValue("IsUsePreallocatedDownloader", value); + } + + public static bool IsUseDownloadSpeedLimiter + { + get => GetAppConfigValue("IsUseDownloadSpeedLimiter").ToBoolNullable() ?? true; + set + { + SetAndSaveConfigValue("IsUseDownloadSpeedLimiter", value); + + _ = DownloadSpeedLimit; + } + } + + public static long DownloadSpeedLimit + { + get => DownloadSpeedLimitCached = GetAppConfigValue("DownloadSpeedLimit").ToLong(); + set => SetAndSaveConfigValue("DownloadSpeedLimit", DownloadSpeedLimitCached = value); + } + + public static int DownloadChunkSize + { + get => GetAppConfigValue("DownloadChunkSize").ToInt(); + set => SetAndSaveConfigValue("DownloadChunkSize", value); + } + + private static long _downloadSpeedLimitCached = 0; // Default: 0 == Unlimited + public static long DownloadSpeedLimitCached + { + get => _downloadSpeedLimitCached; + set + { + _downloadSpeedLimitCached = IsUseDownloadSpeedLimiter ? value : 0; + DownloadSpeedLimitChanged?.Invoke(null, _downloadSpeedLimitCached); + } + } + +#nullable enable + public static event EventHandler? DownloadSpeedLimitChanged; +#nullable restore + private static bool? _cachedIsInstantRegionChange = null; public static bool IsInstantRegionChange { @@ -381,7 +430,11 @@ public static Guid GetGuid(int sessionNum) { "IsAllowHttpRedirections", true }, { "IsAllowHttpCookies", false }, { "IsAllowUntrustedCert", false }, - { "IsBurstDownloadModeEnabled", false }, + { "IsUseDownloadSpeedLimiter", false }, + { "IsUsePreallocatedDownloader", true }, + { "IsBurstDownloadModeEnabled", true }, + { "DownloadSpeedLimit", 0 }, + { "DownloadChunkSize", 4 << 20 }, { "HttpProxyUrl", string.Empty }, { "HttpProxyUsername", string.Empty }, { "HttpProxyPassword", string.Empty }, diff --git a/Hi3Helper.Core/Lang/Locale/LangMisc.cs b/Hi3Helper.Core/Lang/Locale/LangMisc.cs index ddc785ff9..5f78d227b 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMisc.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMisc.cs @@ -127,7 +127,11 @@ public sealed class LangMisc public string IAcceptAgreement { get; set; } = LangFallback?._Misc.IAcceptAgreement; public string IDoNotAcceptAgreement { get; set; } = LangFallback?._Misc.IDoNotAcceptAgreement; - public string ImageCropperTitle { get; set; } = LangFallback?._Misc.ImageCropperTitle; + public string ImageCropperTitle { get; set; } = LangFallback?._Misc.ImageCropperTitle; + + public string IsBytesMoreThanBytes { get; set; } = LangFallback?._Misc.IsBytesMoreThanBytes; + public string IsBytesUnlimited { get; set; } = LangFallback?._Misc.IsBytesUnlimited; + public string IsBytesNotANumber { get; set; } = LangFallback?._Misc.IsBytesNotANumber; } } #endregion diff --git a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs index b8a6a0721..3d323e86b 100644 --- a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs @@ -48,6 +48,8 @@ public sealed class LangSettingsPage public string AppThreads_Attention4 { get; set; } = LangFallback?._SettingsPage.AppThreads_Attention4; public string AppThreads_Attention5 { get; set; } = LangFallback?._SettingsPage.AppThreads_Attention5; public string AppThreads_Attention6 { get; set; } = LangFallback?._SettingsPage.AppThreads_Attention6; + public string AppThreads_AttentionTop1 { get; set; } = LangFallback?._SettingsPage.AppThreads_AttentionTop1; + public string AppThreads_AttentionTop2 { get; set; } = LangFallback?._SettingsPage.AppThreads_AttentionTop2; public string DiscordRPC { get; set; } = LangFallback?._SettingsPage.DiscordRPC; public string DiscordRPC_Toggle { get; set; } = LangFallback?._SettingsPage.DiscordRPC_Toggle; public string DiscordRPC_GameStatusToggle { get; set; } = LangFallback?._SettingsPage.DiscordRPC_GameStatusToggle; @@ -153,6 +155,39 @@ public sealed class LangSettingsPage public string NetworkSettings_ProxyTest_ButtonChecking { get; set; } = LangFallback?._SettingsPage.NetworkSettings_ProxyTest_ButtonChecking; public string NetworkSettings_ProxyTest_ButtonSuccess { get; set; } = LangFallback?._SettingsPage.NetworkSettings_ProxyTest_ButtonSuccess; public string NetworkSettings_ProxyTest_ButtonFailed { get; set; } = LangFallback?._SettingsPage.NetworkSettings_ProxyTest_ButtonFailed; + + public string FileDownloadSettings_Title { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_Title; + public string FileDownloadSettings_SpeedLimit_Title { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimit_Title; + public string FileDownloadSettings_SpeedLimit_NumBox { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimit_NumBox; + public string FileDownloadSettings_SpeedLimitHelp1 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimitHelp1; + public string FileDownloadSettings_SpeedLimitHelp2 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimitHelp2; + public string FileDownloadSettings_SpeedLimitHelp3 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimitHelp3; + public string FileDownloadSettings_SpeedLimitHelp4 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimitHelp4; + public string FileDownloadSettings_SpeedLimitHelp5 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_SpeedLimitHelp5; + + public string FileDownloadSettings_NewPreallocChunk_Title { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunk_Title; + public string FileDownloadSettings_NewPreallocChunk_Subtitle { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunk_Subtitle; + public string FileDownloadSettings_NewPreallocChunk_NumBox { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunk_NumBox; + public string FileDownloadSettings_NewPreallocChunkHelp1 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp1; + public string FileDownloadSettings_NewPreallocChunkHelp2 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp2; + public string FileDownloadSettings_NewPreallocChunkHelp3 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp3; + public string FileDownloadSettings_NewPreallocChunkHelp4 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp4; + public string FileDownloadSettings_NewPreallocChunkHelp5 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp5; + public string FileDownloadSettings_NewPreallocChunkHelp6 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp6; + public string FileDownloadSettings_NewPreallocChunkHelp7 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp7; + public string FileDownloadSettings_NewPreallocChunkHelp8 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp8; + public string FileDownloadSettings_NewPreallocChunkHelp9 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp9; + public string FileDownloadSettings_NewPreallocChunkHelp10 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_NewPreallocChunkHelp10; + + public string FileDownloadSettings_BurstDownload_Title { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownload_Title; + public string FileDownloadSettings_BurstDownload_Subtitle { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownload_Subtitle; + public string FileDownloadSettings_BurstDownloadHelp1 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp1; + public string FileDownloadSettings_BurstDownloadHelp2 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp2; + public string FileDownloadSettings_BurstDownloadHelp3 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp3; + public string FileDownloadSettings_BurstDownloadHelp4 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp4; + public string FileDownloadSettings_BurstDownloadHelp5 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp5; + public string FileDownloadSettings_BurstDownloadHelp6 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp6; + public string FileDownloadSettings_BurstDownloadHelp7 { get; set; } = LangFallback?._SettingsPage.FileDownloadSettings_BurstDownloadHelp7; } } #endregion diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 43bfc1b1d..87c7e7879 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -463,6 +463,8 @@ "AppThreads_Attention4": "the value if you have an existing download as it might", "AppThreads_Attention5": "RE-DOWNLOAD THE ENTIRE THING", "AppThreads_Attention6": "due to the session count needed for the download do not match.", + "AppThreads_AttentionTop1": "The issues below will no longer occur if you have", + "AppThreads_AttentionTop2": "setting enabled.", "DiscordRPC": "Discord Rich Presence", "DiscordRPC_Toggle": "Show Discord Presence", @@ -578,7 +580,40 @@ "NetworkSettings_Http_Redirect": "Allow HTTP Redirection", "NetworkSettings_Http_SimulateCookies": "Allow Simulate HTTP Cookies", "NetworkSettings_Http_UntrustedHttps": "Allow Untrusted HTTPS Certificate", - "NetworkSettings_Http_Timeout": "HTTP Client Timeout (in Seconds)" + "NetworkSettings_Http_Timeout": "HTTP Client Timeout (in Seconds)", + + "FileDownloadSettings_Title": "File Download Settings", + "FileDownloadSettings_SpeedLimit_Title": "Limit Download Speed", + "FileDownloadSettings_SpeedLimit_NumBox": "Speed Limit (in MiB unit)", + "FileDownloadSettings_SpeedLimitHelp1": "Default:", + "FileDownloadSettings_SpeedLimitHelp2": "Speed Limit value range:", + "FileDownloadSettings_SpeedLimitHelp3": "1 - 1000 MiB/s", + "FileDownloadSettings_SpeedLimitHelp4": "Limiting the bandwidth for download. This setting cannot be used alongside", + "FileDownloadSettings_SpeedLimitHelp5": "setting.", + + "FileDownloadSettings_NewPreallocChunk_Title": "New Pre-allocated Downloader", + "FileDownloadSettings_NewPreallocChunk_Subtitle": "For Game Installation, Game Repair and Cache Updates only.", + "FileDownloadSettings_NewPreallocChunk_NumBox": "Chunk Size (in MiB unit)", + "FileDownloadSettings_NewPreallocChunkHelp1": "Default:", + "FileDownloadSettings_NewPreallocChunkHelp2": "Chunk Size value range:", + "FileDownloadSettings_NewPreallocChunkHelp3": "1 - 32 MiB", + "FileDownloadSettings_NewPreallocChunkHelp4": "When enabled, the downloader will dynamically pre-allocate the size of the file while downloading it.", + "FileDownloadSettings_NewPreallocChunkHelp5": "This enables the downloader to directly writes data chunks into the file without splitting it into separate files.", + "FileDownloadSettings_NewPreallocChunkHelp6": "When disabled, the launcher will use an old method where data chunks will be written into separate files. It will also disable", + "FileDownloadSettings_NewPreallocChunkHelp7": "and", + "FileDownloadSettings_NewPreallocChunkHelp8": "settings.", + "FileDownloadSettings_NewPreallocChunkHelp9": "Note:", + "FileDownloadSettings_NewPreallocChunkHelp10": "This feature is only available for Game Installation (including Initial Install, Update and Preload), Game Repair and Cache Update.", + + "FileDownloadSettings_BurstDownload_Title": "Burst File-Download Mode", + "FileDownloadSettings_BurstDownload_Subtitle": "For Game Repair and Cache Updates only.", + "FileDownloadSettings_BurstDownloadHelp1": "Default:", + "FileDownloadSettings_BurstDownloadHelp2": "When enabled, this feature will allow the download process on", + "FileDownloadSettings_BurstDownloadHelp3": "and", + "FileDownloadSettings_BurstDownloadHelp4": "feature to run in parallel at the same time to speed up the download process.", + "FileDownloadSettings_BurstDownloadHelp5": "When disabled, the download process on", + "FileDownloadSettings_BurstDownloadHelp6": "and", + "FileDownloadSettings_BurstDownloadHelp7": "will use a sequential download process." }, "_Misc": { @@ -706,7 +741,11 @@ "LauncherNameSteam": "Steam", "LauncherNameUnknown": "(Launcher Name is Unknown)", - "ImageCropperTitle": "Crop the image" + "ImageCropperTitle": "Crop the image", + + "IsBytesMoreThanBytes": "= {0} bytes (-/+ {1})", + "IsBytesUnlimited": "= Unlimited", + "IsBytesNotANumber": "= NaN" }, "_BackgroundNotification": { From 053e1ea462917e75bc83f7139f5cd4fe924667fa Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Thu, 29 Aug 2024 23:17:50 +0700 Subject: [PATCH 14/25] Use newly ``DownloadClient`` for ``InstallManagerBase`` --- .../BaseClass/GameInstallPackage.cs | 31 +-- .../BaseClass/InstallManagerBase.cs | 195 +++++++++++++++--- .../Classes/Interfaces/Class/ProgressBase.cs | 48 +++-- Hi3Helper.Http | 2 +- 4 files changed, 214 insertions(+), 62 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs index 08556982f..4420f336a 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs @@ -15,21 +15,22 @@ namespace CollapseLauncher.InstallManager internal class GameInstallPackage : IAssetIndexSummary { #region Properties - public string URL { get; set; } - public string DecompressedURL { get; set; } - public string Name { get; set; } - public string PathOutput { get; set; } - public GameInstallPackageType PackageType { get; set; } - public long Size { get; set; } - public long SizeRequired { get; set; } - public long SizeDownloaded { get; set; } - public GameVersion Version { get; set; } - public byte[] Hash { get; set; } - public string HashString { get => HexTool.BytesToHexUnsafe(Hash); } - public string LanguageID { get; set; } - public List Segments { get; set; } - public string RunCommand { get; set; } - public string PluginId { get; set; } + public string URL { get; set; } + public string DecompressedURL { get; set; } + public string Name { get; set; } + public string PathOutput { get; set; } + public GameInstallPackageType PackageType { get; set; } + public long Size { get; set; } + public long SizeRequired { get; set; } + public long SizeDownloaded { get; set; } + public GameVersion Version { get; set; } + public byte[] Hash { get; set; } + public string HashString { get => HexTool.BytesToHexUnsafe(Hash); } + public string LanguageID { get; set; } + public List Segments { get; set; } + public string RunCommand { get; set; } + public string PluginId { get; set; } + public bool IsUseLegacyDownloader { get; set; } #endregion public GameInstallPackage(RegionResourcePlugin packageProperty, string pathOutput) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 98863b15d..45e3e7c9e 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -14,6 +14,7 @@ // ReSharper disable ConvertToPrimaryConstructor using CollapseLauncher.CustomControls; +using CollapseLauncher.Dialogs; using CollapseLauncher.Extension; using CollapseLauncher.FileDialogCOM; using CollapseLauncher.Helper; @@ -23,6 +24,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetIndex; +using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Shared.Region; @@ -59,7 +61,6 @@ using SophonLogger = Hi3Helper.Sophon.Helper.Logger; using SophonManifest = Hi3Helper.Sophon.SophonManifest; -using CollapseLauncher.Dialogs; // ReSharper disable ForCanBeConvertedToForeach // ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault @@ -522,6 +523,15 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List x!.Segments != null ? x.Segments.Count : 1) > 1; _status.IsProgressPerFileIndetermined = true; @@ -533,7 +543,7 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List x!.Size); - _progressAllSizeCurrent = GetExistingDownloadPackageSize(gamePackage); + _progressAllSizeCurrent = await GetExistingDownloadPackageSize(downloadClient, gamePackage, _token!.Token); // Sanitize Check: Check for the free space of the drive and show the dialog if necessary await CheckDriveFreeSpace(_parentUI, gamePackage, _progressAllSizeCurrent); @@ -547,7 +557,7 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List x!.Segments != null ? x.Segments.Count : 1) > 1; @@ -669,7 +688,7 @@ public virtual async Task StartPackageDownload(bool skipDialog) // Get the remote total size and current total size _progressAllSizeTotal = _assetIndex!.Sum(x => x!.Size); - _progressAllSizeCurrent = GetExistingDownloadPackageSize(_assetIndex); + _progressAllSizeCurrent = await GetExistingDownloadPackageSize(downloadClient, _assetIndex, _token!.Token); // Sanitize Check: Check for the free space of the drive and show the dialog if necessary await CheckDriveFreeSpace(_parentUI, _assetIndex, _progressAllSizeCurrent); @@ -681,7 +700,7 @@ public virtual async Task StartPackageDownload(bool skipDialog) } // Start downloading process - await InvokePackageDownloadRoutine(_assetIndex, _token!.Token); + await InvokePackageDownloadRoutine(downloadClient, _assetIndex, _token!.Token); } // Bool: 0 -> Indicates that one of the package is failing and need to redownload @@ -3470,7 +3489,8 @@ private void TryRemoveRedundantHDiffList() #region Private Methods - StartPackageDownload - private async ValueTask InvokePackageDownloadRoutine(List packageList, + private async ValueTask InvokePackageDownloadRoutine(DownloadClient downloadClient, + List packageList, CancellationToken token) { // Get the package/segment count @@ -3481,16 +3501,15 @@ private async ValueTask InvokePackageDownloadRoutine(List pa _progressAllCountTotal = packageCount; RestartStopwatch(); - // Initialize new proxy-aware HttpClient - using HttpClient httpClientNew = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) - .SetAllowedDecompression(DecompressionMethods.None) - .Create(); - - using Http _httpClient = new Http(true, customHttpClient: httpClientNew); + // Initialize a legacy Http as well + using Http _httpClient = new Http(true, customHttpClient: downloadClient.GetHttpClient()); // Subscribe the download progress to the event adapter _httpClient.DownloadProgress += HttpClientDownloadProgressAdapter; + + // Create a speed limiter instance for the new DownloadClient and register it + DownloadSpeedLimiter speedLimiter = DownloadSpeedLimiter.CreateInstance(LauncherConfig.DownloadSpeedLimitCached); + LauncherConfig.DownloadSpeedLimitChanged += speedLimiter.GetListener(); try { // Iterate the package list @@ -3502,7 +3521,7 @@ private async ValueTask InvokePackageDownloadRoutine(List pa // Iterate the segment list for (int i = 0; i < package.Segments.Count; i++) { - await RunPackageDownloadRoutine(_httpClient, package.Segments[i], token, packageCount); + await RunPackageDownloadRoutine(_httpClient, downloadClient, speedLimiter, package.Segments[i], token, packageCount); } // Skip action below and continue to the next segment @@ -3510,19 +3529,25 @@ private async ValueTask InvokePackageDownloadRoutine(List pa } // Else, run the routine as normal - await RunPackageDownloadRoutine(_httpClient, package, token, packageCount); + await RunPackageDownloadRoutine(_httpClient, downloadClient, speedLimiter, package, token, packageCount); } } finally { // Unsubscribe the download progress from the event adapter _httpClient.DownloadProgress -= HttpClientDownloadProgressAdapter; + + // Unregister the speed limiter + LauncherConfig.DownloadSpeedLimitChanged -= speedLimiter.GetListener(); } } - private async ValueTask RunPackageDownloadRoutine(Http httpClient, - GameInstallPackage package, CancellationToken token, - int packageCount) + private async ValueTask RunPackageDownloadRoutine(Http httpClient, + DownloadClient downloadClient, + DownloadSpeedLimiter downloadSpeedLimiter, + GameInstallPackage package, + CancellationToken token, + int packageCount) { // Set the activity status _status.IsIncludePerFileIndicator = packageCount > 1; @@ -3539,16 +3564,37 @@ private async ValueTask RunPackageDownloadRoutine(Http httpClient, if (!isExistingPackageFileExist || existingPackageFileSize != package.Size) { - // If the package size is more than or equal to 10 MB, then allow to use multi-session. - // Otherwise, forcefully use single-session. - bool isCanMultiSession = package.Size >= 10 << 20; - if (isCanMultiSession) + // Get the file path + string filePath = EnsureCreationOfDirectory(package.PathOutput); + + bool isCanMultiSession = false; + // If a legacy downloader is used, then use the legacy Http downloader + if (package.IsUseLegacyDownloader) { - await httpClient.Download(package.URL, EnsureCreationOfDirectory(package.PathOutput), _downloadThreadCount, false, token); + // If the package size is more than or equal to 10 MB, then allow to use multi-session. + // Otherwise, forcefully use single-session. + isCanMultiSession = package.Size >= 10 << 20; + if (isCanMultiSession) + { + await httpClient.Download(package.URL, filePath, _downloadThreadCount, false, token); + } + else + { + await httpClient.Download(package.URL, filePath, false, null, null, token); + } } + // Otherwise, use the new downloder else { - await httpClient.Download(package.URL, EnsureCreationOfDirectory(package.PathOutput), false, null, null, token); + // Run the new downloader + await RunDownloadTask( + package.Size, + filePath, + package.URL, + downloadClient, + HttpClientDownloadProgressAdapter, + token, + false); } // Update status to merging @@ -3559,8 +3605,8 @@ private async ValueTask RunPackageDownloadRoutine(Http httpClient, _stopwatch.Stop(); // Check if the merge chunk is enabled and the download could perform multisession, - // then do merge. - if (_canMergeDownloadChunks && isCanMultiSession) + // then do merge (also if legacy downloader is used). + if (_canMergeDownloadChunks && isCanMultiSession && package.IsUseLegacyDownloader) { await httpClient.Merge(token); } @@ -3628,11 +3674,21 @@ private void DeleteDownloadedFile(string FileOutput, byte Thread) fileInfo.Delete(); } + // Get the info of the new downloader metadata + FileInfo fileInfoMetadata = new FileInfo(FileOutput + ".collapseMeta"); + + // If metadata file existed, then delete + if (fileInfoMetadata.Exists) + { + fileInfoMetadata.IsReadOnly = false; + fileInfoMetadata.Delete(); + } + // Delete the file of the chunk file too Http.DeleteMultisessionFiles(FileOutput, Thread); } - private long GetExistingDownloadPackageSize(List packageList) + private async ValueTask GetExistingDownloadPackageSize(DownloadClient downloadClient, List packageList, CancellationToken token) { // Initialize total existing size and download thread count long totalSize = 0; @@ -3649,19 +3705,37 @@ private long GetExistingDownloadPackageSize(List packageList Http .CalculateExistingMultisessionFilesWithExpctdSize(packageList[i].Segments[j].PathOutput, _downloadThreadCount, packageList[i].Segments[j].Size); - totalSize += segmentDownloaded; - totalSegmentDownloaded += segmentDownloaded; - packageList[i].Segments[j].SizeDownloaded = segmentDownloaded; + + long newSegmentedDownloaded = await downloadClient.GetDownloadedFileSize( + packageList[i].Segments[j].URL, + packageList[i].Segments[j].PathOutput, + packageList[i].Segments[j].Size, + token + ); + bool isUseLegacySegmentedSize = segmentDownloaded > newSegmentedDownloaded || newSegmentedDownloaded > packageList[i].Segments[j].Size; + totalSize += isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; + totalSegmentDownloaded += isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; + packageList[i].Segments[j].SizeDownloaded = isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; + packageList[i].Segments[j].IsUseLegacyDownloader = isUseLegacySegmentedSize; } packageList[i].SizeDownloaded = totalSegmentDownloaded; continue; } - packageList[i].SizeDownloaded = + long legacyDownloadedSize = Http.CalculateExistingMultisessionFilesWithExpctdSize(packageList[i].PathOutput, _downloadThreadCount, packageList[i].Size); - totalSize += packageList[i].SizeDownloaded; + long newDownloaderSize = await downloadClient.GetDownloadedFileSize( + packageList[i].URL, + packageList[i].PathOutput, + packageList[i].Size, + token + ); + bool isUseLegacySize = legacyDownloadedSize > newDownloaderSize || newDownloaderSize > packageList[i].Size; + packageList[i].IsUseLegacyDownloader = isUseLegacySize; + packageList[i].SizeDownloaded = isUseLegacySize ? legacyDownloadedSize : newDownloaderSize; + totalSize += packageList[i].SizeDownloaded; } // return totalSize @@ -3936,6 +4010,63 @@ private long GetLastSize(long input) return a; } + private async void HttpClientDownloadProgressAdapter(int read, DownloadProgress downloadProgress) + { + // Set the progress bar not indetermined + _status.IsProgressPerFileIndetermined = false; + _status.IsProgressAllIndetermined = false; + + // Increment the total current size if status is not merging + Interlocked.Add(ref _progressAllSizeCurrent, read); + // Increment the total last read + Interlocked.Add(ref _progressAllIOReadCurrent, read); + + if (_refreshStopwatch!.ElapsedMilliseconds > _refreshInterval) + { + // Assign local sizes to progress + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; + + // Calculate the speed + double speedPerFile = _progressAllIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speedAllFile = (_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); + _progress.ProgressAllSpeed = speedPerFile.ClampLimitedSpeedNumber(); + + // Calculate percentage + _progress.ProgressPerFilePercentage = + Math.Round(_progressPerFileSizeCurrent / (double)_progressPerFileSizeTotal * 100, 2); + _progress.ProgressAllPercentage = + Math.Round(_progressAllSizeCurrent / (double)_progressAllSizeTotal * 100, 2); + + // Calculate the timelapse + _progress.ProgressAllTimeLeft = + ((_progressAllSizeTotal - _progressAllSizeCurrent) / speedAllFile.Unzeroed()) + .ToTimeSpanNormalized(); + + // Update the status of per file size and current progress from Http client + _progressPerFileSizeCurrent = downloadProgress.BytesDownloaded; + _progressPerFileSizeTotal = downloadProgress.BytesTotal; + _progress.ProgressPerFilePercentage = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal); + + lock (_downloadSpeedRefreshStopwatch) + { + if (_downloadSpeedRefreshInterval < _downloadSpeedRefreshStopwatch!.ElapsedMilliseconds) + { + _progressAllIOReadCurrent = 0; + _downloadSpeedRefreshStopwatch.Restart(); + } + } + + // Update the status + UpdateAll(); + + _refreshStopwatch.Restart(); + await Task.Delay(_refreshInterval); + } + } + private async void HttpClientDownloadProgressAdapter(object sender, DownloadEvent e) { // Set the progress bar not indetermined diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 281af07f5..7b3f917a2 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -64,7 +64,7 @@ private void Init() protected long _progressAllSizeCurrent; protected long _progressAllSizeFound; protected long _progressAllSizeTotal; - protected double _progressAllIOReadCurrent; + protected long _progressAllIOReadCurrent; protected long _progressPerFileSizeCurrent; protected long _progressPerFileSizeTotal; protected double _progressPerFileIOReadCurrent; @@ -955,20 +955,40 @@ protected bool SummarizeStatusAndProgress(List assetIndex, string msgIfFound #nullable enable protected virtual async Task RunDownloadTask(long assetSize, string assetPath, string assetURL, - DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, bool isOverwrite = true, - EventHandler? downloadSpeedLimitChanged = null) + DownloadClient downloadClient, DownloadProgressDelegate downloadProgress, CancellationToken token, bool isOverwrite = true) { - // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) - await downloadClient.DownloadAsync( - assetURL, - EnsureCreationOfDirectory(assetPath), - isOverwrite, - sessionChunkSize: LauncherConfig.DownloadChunkSize, - progressDelegateAsync: downloadProgress, - cancelToken: token, - downloadSpeedLimitEvent: downloadSpeedLimitChanged, - initialDownloadSpeed: LauncherConfig.DownloadSpeedLimitCached - ); + // For any instances that uses Burst Download and if the speed limiter is null when + // _isBurstDownloadEnabled set to false, then create the speed limiter instance + bool isUseSelfSpeedLimiter = !_isBurstDownloadEnabled; + DownloadSpeedLimiter? downloadSpeedLimiter = null; + if (isUseSelfSpeedLimiter) + { + // Create the speed limiter instance and register the listener + downloadSpeedLimiter = DownloadSpeedLimiter.CreateInstance(LauncherConfig.DownloadSpeedLimitCached); + LauncherConfig.DownloadSpeedLimitChanged += downloadSpeedLimiter.GetListener(); + } + + try + { + // Always do multi-session download with the new DownloadClient regardless of any sizes (if applicable) + await downloadClient.DownloadAsync( + assetURL, + EnsureCreationOfDirectory(assetPath), + isOverwrite, + sessionChunkSize: LauncherConfig.DownloadChunkSize, + progressDelegateAsync: downloadProgress, + cancelToken: token, + downloadSpeedLimiter: downloadSpeedLimiter + ); + } + finally + { + // If the self speed listener is used, then unregister the listener + if (isUseSelfSpeedLimiter && downloadSpeedLimiter != null) + { + LauncherConfig.DownloadSpeedLimitChanged -= downloadSpeedLimiter?.GetListener(); + } + } } #nullable restore diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 2dc55ec52..2af2e27a3 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 2dc55ec52841201cbbf9d8047d6f412a89c259b9 +Subproject commit 2af2e27a3ed5f40a10c38246dc333cd241aec263 From 8e5e92d876b7607c0a308fa0c59a37d836bf112a Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Thu, 29 Aug 2024 23:54:38 +0700 Subject: [PATCH 15/25] Enforce ``IsUsePreallocatedDownloader`` on ``InstallManagerBase`` --- .../BaseClass/InstallManagerBase.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 45e3e7c9e..454803b08 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -3507,9 +3507,6 @@ private async ValueTask InvokePackageDownloadRoutine(DownloadClient do // Subscribe the download progress to the event adapter _httpClient.DownloadProgress += HttpClientDownloadProgressAdapter; - // Create a speed limiter instance for the new DownloadClient and register it - DownloadSpeedLimiter speedLimiter = DownloadSpeedLimiter.CreateInstance(LauncherConfig.DownloadSpeedLimitCached); - LauncherConfig.DownloadSpeedLimitChanged += speedLimiter.GetListener(); try { // Iterate the package list @@ -3521,7 +3518,7 @@ private async ValueTask InvokePackageDownloadRoutine(DownloadClient do // Iterate the segment list for (int i = 0; i < package.Segments.Count; i++) { - await RunPackageDownloadRoutine(_httpClient, downloadClient, speedLimiter, package.Segments[i], token, packageCount); + await RunPackageDownloadRoutine(_httpClient, downloadClient, package.Segments[i], token, packageCount); } // Skip action below and continue to the next segment @@ -3529,22 +3526,18 @@ private async ValueTask InvokePackageDownloadRoutine(DownloadClient do } // Else, run the routine as normal - await RunPackageDownloadRoutine(_httpClient, downloadClient, speedLimiter, package, token, packageCount); + await RunPackageDownloadRoutine(_httpClient, downloadClient, package, token, packageCount); } } finally { // Unsubscribe the download progress from the event adapter _httpClient.DownloadProgress -= HttpClientDownloadProgressAdapter; - - // Unregister the speed limiter - LauncherConfig.DownloadSpeedLimitChanged -= speedLimiter.GetListener(); } } private async ValueTask RunPackageDownloadRoutine(Http httpClient, DownloadClient downloadClient, - DownloadSpeedLimiter downloadSpeedLimiter, GameInstallPackage package, CancellationToken token, int packageCount) @@ -3712,7 +3705,9 @@ private async ValueTask GetExistingDownloadPackageSize(DownloadClient down packageList[i].Segments[j].Size, token ); - bool isUseLegacySegmentedSize = segmentDownloaded > newSegmentedDownloaded || newSegmentedDownloaded > packageList[i].Segments[j].Size; + bool isUseLegacySegmentedSize = !LauncherConfig.IsUsePreallocatedDownloader + || segmentDownloaded > newSegmentedDownloaded + || newSegmentedDownloaded > packageList[i].Segments[j].Size; totalSize += isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; totalSegmentDownloaded += isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; packageList[i].Segments[j].SizeDownloaded = isUseLegacySegmentedSize ? segmentDownloaded : newSegmentedDownloaded; @@ -3732,7 +3727,9 @@ private async ValueTask GetExistingDownloadPackageSize(DownloadClient down packageList[i].Size, token ); - bool isUseLegacySize = legacyDownloadedSize > newDownloaderSize || newDownloaderSize > packageList[i].Size; + bool isUseLegacySize = !LauncherConfig.IsUsePreallocatedDownloader + || legacyDownloadedSize > newDownloaderSize + || newDownloaderSize > packageList[i].Size; packageList[i].IsUseLegacyDownloader = isUseLegacySize; packageList[i].SizeDownloaded = isUseLegacySize ? legacyDownloadedSize : newDownloaderSize; totalSize += packageList[i].SizeDownloaded; From 5a1f3aa84d54c1b5e76520d37ed6e7f0da75a8fb Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 30 Aug 2024 00:04:38 +0700 Subject: [PATCH 16/25] Code QA --- CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs | 2 +- .../Classes/InstallManagement/BaseClass/GameInstallPackage.cs | 1 + .../BaseClass/InstallManagerBase.PkgVersion.cs | 1 - CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs | 2 +- .../XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs | 2 +- CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml | 1 - CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs | 1 - Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs | 1 - 8 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index 44198700f..636565d13 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -89,7 +89,7 @@ private async Task UpdateCacheAsset((SRAsset AssetIndex, IAssetProperty AssetPro UpdateAll(); // Run download task - await RunDownloadTask(asset.AssetIndex.Size, asset.AssetIndex.LocalName, asset.AssetIndex.RemoteURL, downloadClient, downloadProgress, token); + await RunDownloadTask(asset.AssetIndex.Size, asset.AssetIndex.LocalName!, asset.AssetIndex.RemoteURL, downloadClient, downloadProgress, token); LogWriteLine($"Downloaded cache [T: {asset.AssetIndex.AssetType}]: {Path.GetFileName(asset.AssetIndex.LocalName)}", LogType.Default, true); // Remove Asset Entry display diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs index 4420f336a..8e6546f62 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/GameInstallPackage.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; +#pragma warning disable CS0618 // Type or member is obsolete namespace CollapseLauncher.InstallManager { [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 5547f033e..cd88dae7f 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -5,7 +5,6 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.Http; -using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Animation; diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 7b3f917a2..f3b5aff28 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -986,7 +986,7 @@ await downloadClient.DownloadAsync( // If the self speed listener is used, then unregister the listener if (isUseSelfSpeedLimiter && downloadSpeedLimiter != null) { - LauncherConfig.DownloadSpeedLimitChanged -= downloadSpeedLimiter?.GetListener(); + LauncherConfig.DownloadSpeedLimitChanged -= downloadSpeedLimiter.GetListener(); } } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs index 55ad21d68..7167c037b 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/InstallationConvert.xaml.cs @@ -182,7 +182,7 @@ private async Task FetchDataIntegrityURL(PresetConfig Profile) RegionResourceProp _Entry; _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default, tokenSource.Token); - GameVersion = _Entry.data.game.latest.version; + GameVersion = _Entry.data?.game?.latest?.version; return _RepoList[GameVersion ?? throw new InvalidOperationException()]; } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml index 493bc6bf1..605b4e234 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml @@ -9,7 +9,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ext="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" - xmlns:helperData="using:Hi3Helper.Data" xmlns:innerConfig="using:Hi3Helper.Shared.Region" xmlns:localWindowSize="using:CollapseLauncher.WindowSize" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs index f2cb25bc9..fdcc7fb27 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs @@ -40,7 +40,6 @@ using MediaType = CollapseLauncher.Helper.Background.BackgroundMediaUtility.MediaType; using TaskSched = Microsoft.Win32.TaskScheduler.Task; using Task = System.Threading.Tasks.Task; -using Windows.Storage.Search; // ReSharper disable CheckNamespace // ReSharper disable PossibleNullReferenceException diff --git a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs index e599385f6..f8780bf75 100644 --- a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs +++ b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs @@ -1,5 +1,4 @@ using Hi3Helper.Data; -using Hi3Helper.Http; using Hi3Helper.Screen; using Hi3Helper.Shared.ClassStruct; using System; From eb1348cec58863bb283dad7268dc0512ee346dc4 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 30 Aug 2024 00:14:57 +0700 Subject: [PATCH 17/25] Code QA (hopefully?) --- .../RepairManagement/StarRail/Fetch.cs | 35 ++++++++----------- .../RepairManagement/StarRail/Repair.cs | 4 +-- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index 9e30cdcf0..3d6a596d0 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -104,7 +104,7 @@ private async Task Fetch(List assetIndex, CancellationToke // Initialize the metadata tool (including dispatcher and gateway). // Perform this if only base._isVersionOverride is false to indicate that the repair performed is // not for delta patch integrity check. - if (!base._isVersionOverride && !this._isOnlyRecoverMain && await _innerGameVersionManager.StarRailMetadataTool.Initialize(token, downloadClient, _httpClient_FetchAssetProgress, GetExistingGameRegionID(), Path.Combine(_gamePath, $"{Path.GetFileNameWithoutExtension(_innerGameVersionManager.GamePreset.GameExecutableName)}_Data\\Persistent"))) + if (!_isVersionOverride && !this._isOnlyRecoverMain && await _innerGameVersionManager.StarRailMetadataTool.Initialize(token, downloadClient, _httpClient_FetchAssetProgress, GetExistingGameRegionID(), Path.Combine(_gamePath, $"{Path.GetFileNameWithoutExtension(_innerGameVersionManager.GamePreset.GameExecutableName)}_Data\\Persistent"))) { // Read block metadata and convert to FilePropertiesRemote await _innerGameVersionManager.StarRailMetadataTool.ReadAsbMetadataInformation(downloadClient, _httpClient_FetchAssetProgress, token); @@ -223,35 +223,28 @@ private void ConvertPkgVersionToAssetIndex(List pkgVersion #endregion #region Utilities - private FilePropertiesRemote GetNormalizedFilePropertyTypeBased(string remoteAbsolutePath, string remoteRelativePath, long fileSize, - string hash, FileType type, bool isPatchApplicable, bool isHasHashMark) => - GetNormalizedFilePropertyTypeBased(remoteAbsolutePath, remoteRelativePath, fileSize, - hash, type, false, isPatchApplicable, isHasHashMark); - private FilePropertiesRemote GetNormalizedFilePropertyTypeBased(string remoteParentURL, string remoteRelativePath, long fileSize, string hash, FileType type = FileType.Generic, - bool isPkgVersion = true, bool isPatchApplicable = false, bool isHasHashMark = false) { - string localAbsolutePath, - remoteAbsolutePath = type switch - { - FileType.Generic => CombineURLFromString(remoteParentURL, remoteRelativePath), - _ => remoteParentURL - }, + string remoteAbsolutePath = type switch + { + FileType.Generic => CombineURLFromString(remoteParentURL, remoteRelativePath), + _ => remoteParentURL + }, typeAssetRelativeParentPath = string.Format(type switch - { - FileType.Blocks => _assetGameBlocksStreamingPath, - FileType.Audio => _assetGameAudioStreamingPath, - FileType.Video => _assetGameVideoStreamingPath, - _ => string.Empty - }, _execName); - - localAbsolutePath = Path.Combine(_gamePath, typeAssetRelativeParentPath, NormalizePath(remoteRelativePath)); + { + FileType.Blocks => _assetGameBlocksStreamingPath, + FileType.Audio => _assetGameAudioStreamingPath, + FileType.Video => _assetGameVideoStreamingPath, + _ => string.Empty + }, _execName); + + var localAbsolutePath = Path.Combine(_gamePath, typeAssetRelativeParentPath, NormalizePath(remoteRelativePath)); return new FilePropertiesRemote { diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs index d9bd0076e..6fe5cf417 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs @@ -110,7 +110,7 @@ private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAss // If asset type is unused, then delete it if (asset.AssetIndex.FT == FileType.Unused) { - FileInfo fileInfo = new FileInfo(asset.AssetIndex.N); + FileInfo fileInfo = new FileInfo(asset.AssetIndex.N!); if (fileInfo.Exists) { fileInfo.IsReadOnly = false; @@ -122,7 +122,7 @@ private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAss else { // Start asset download task - await RunDownloadTask(asset.AssetIndex.S, asset.AssetIndex.N, asset.AssetIndex.RN, downloadClient, downloadProgress, token); + await RunDownloadTask(asset.AssetIndex.S, asset.AssetIndex.N!, asset.AssetIndex.RN, downloadClient, downloadProgress, token); LogWriteLine($"File [T: {asset.AssetIndex.FT}] {(asset.AssetIndex.FT == FileType.Blocks ? asset.AssetIndex.CRC : asset.AssetIndex.N)} has been downloaded!", LogType.Default, true); } From 94f9b3e02f7eb91340887b5c461971bdff7c1d93 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Fri, 30 Aug 2024 21:45:39 +0700 Subject: [PATCH 18/25] CodeQA Co-authored-by: Kemal Setya Adhi --- Hi3Helper.EncTool.Test/Program.cs | 44 +++++++++++++++---------------- Hi3Helper.Http | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Hi3Helper.EncTool.Test/Program.cs b/Hi3Helper.EncTool.Test/Program.cs index 2ae16e339..4ef7d0905 100644 --- a/Hi3Helper.EncTool.Test/Program.cs +++ b/Hi3Helper.EncTool.Test/Program.cs @@ -12,20 +12,20 @@ static async Task Main(string[] args) return; //frick testing #region unused - using (SRMetadata srm = new SRMetadata("https://globaldp-prod-os01.starrails.com/query_dispatch", "3a57430d8d", - "?version={0}{1}&t={2}&language_type=3&platform_type=3&channel_id=1&sub_channel_id=1&is_new_format=1", - "?version={0}{1}&t={2}&uid=0&language_type=3&platform_type=3&dispatch_seed={3}&channel_id=1&sub_channel_id=1&is_need_url=1", - "OSPRODWin", "1.5.0")) - { - await srm.Initialize(default, "prod_official_asia", "F:\\CollapseData\\SRGlb\\Games\\StarRail_Data\\Persistent"); - await srm.ReadAsbMetadataInformation(default); - await srm.ReadBlockMetadataInformation(default); - await srm.ReadAudioMetadataInformation(default); - await srm.ReadVideoMetadataInformation(default); - await srm.ReadIFixMetadataInformation(default); - await srm.ReadDesignMetadataInformation(default); - await srm.ReadLuaMetadataInformation(default); - } + // using (SRMetadata srm = new SRMetadata("https://globaldp-prod-os01.starrails.com/query_dispatch", "3a57430d8d", + // "?version={0}{1}&t={2}&language_type=3&platform_type=3&channel_id=1&sub_channel_id=1&is_new_format=1", + // "?version={0}{1}&t={2}&uid=0&language_type=3&platform_type=3&dispatch_seed={3}&channel_id=1&sub_channel_id=1&is_need_url=1", + // "OSPRODWin", "1.5.0")) + // { + // await srm.Initialize(default, "prod_official_asia", "F:\\CollapseData\\SRGlb\\Games\\StarRail_Data\\Persistent"); + // await srm.ReadAsbMetadataInformation(default); + // await srm.ReadBlockMetadataInformation(default); + // await srm.ReadAudioMetadataInformation(default); + // await srm.ReadVideoMetadataInformation(default); + // await srm.ReadIFixMetadataInformation(default); + // await srm.ReadDesignMetadataInformation(default); + // await srm.ReadLuaMetadataInformation(default); + // } return; @@ -46,14 +46,14 @@ static async Task Main(string[] args) string URL = "https://d2wztyirwsuyyo.cloudfront.net/ptpublic/bh3_global/20230109172630_NRBvwa9qTB4EUXIz/extract/BH3_Data/StreamingAssets/Asb/pc/0fae6f42832ea16a84ac496c38437a1d.wmv"; path = @"C:\Program Files\Honkai Impact 3rd glb\Games\BH3_Data\StreamingAssets\Asb\pc\0fae6f42832ea16a84ac496c38437a1d.wmv"; - using (FileStream fs = new(path, FileMode.Open, FileAccess.Write)) - { - using (ChunkStream cs = new(fs, 0, 457688, false)) - { - Http.Http client = new(false); - await client.Download(URL, cs, 0, 457688, default, true); - } - } + // using (FileStream fs = new(path, FileMode.Open, FileAccess.Write)) + // { + // using (ChunkStream cs = new(fs, 0, 457688, false)) + // { + // Http.Http client = new(false); + // await client.Download(URL, cs, 0, 457688, default, true); + // } + // } /* XMFParser parser = new XMFParser(path); diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 2af2e27a3..2c6e2a52d 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 2af2e27a3ed5f40a10c38246dc333cd241aec263 +Subproject commit 2c6e2a52d16b0c0d8a57a6c1a8f498d8b67cc14e From 7cfcee1e39ff974d3ad9ab5f6a2c30eb8bca815f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 30 Aug 2024 23:50:57 +0700 Subject: [PATCH 19/25] Adjust reserved thread count as a const --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 2 +- .../Classes/InstallManagement/BaseClass/InstallManagerBase.cs | 4 ++-- CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs | 2 +- CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs | 2 +- CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs | 2 +- CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs | 4 ++-- CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs | 2 +- CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index cd88dae7f..9392901a0 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -169,7 +169,7 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc { // Initialize new proxy-aware HttpClient using HttpClient httpClient = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 454803b08..2c385d5e1 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -525,7 +525,7 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List> Fetch(List Repair(List repairAssetIndex, Can // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 211901391..1b4b9f678 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -64,7 +64,7 @@ private async Task Fetch(List assetIndex, CancellationToke // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs index f916fc17f..43dac1eed 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs @@ -33,7 +33,7 @@ private async Task Repair(List repairAssetIndex, Can // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); @@ -156,7 +156,7 @@ private async Task RepairAssetTypeGeneric((FilePropertiesRemote AssetIndex, IAss true); // Set URL of the asset - string assetURL = customURL != null ? customURL : asset.AssetIndex.RN; + string assetURL = customURL ?? asset.AssetIndex.RN; string assetPath = Path.Combine(_gamePath, ConverterTool.NormalizePath(asset.AssetIndex.N)); if (asset.AssetIndex.FT == FileType.Unused && !_isOnlyRecoverMain) diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index 3d6a596d0..c89e51829 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -72,7 +72,7 @@ private async Task Fetch(List assetIndex, CancellationToke // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs index 6fe5cf417..32c1961ba 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs @@ -32,7 +32,7 @@ private async Task Repair(List repairAssetIndex, Can // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); From 30f1b396c1e76335634f4d121e11823a898b8f9f Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 30 Aug 2024 23:53:47 +0700 Subject: [PATCH 20/25] Locale Grammar Correction Co-Authored-By: Ron Friedman <9833218+Cryotechnic@users.noreply.github.com> --- Hi3Helper.Core/Lang/en_US.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index ceeb28230..f262f4872 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -585,40 +585,40 @@ "NetworkSettings_Http_Redirect": "Allow HTTP Redirection", "NetworkSettings_Http_SimulateCookies": "Allow Simulate HTTP Cookies", "NetworkSettings_Http_UntrustedHttps": "Allow Untrusted HTTPS Certificate", - "NetworkSettings_Http_Timeout": "HTTP Client Timeout (in Seconds)", + "NetworkSettings_Http_Timeout": "HTTP Client Timeout (in seconds)", "FileDownloadSettings_Title": "File Download Settings", "FileDownloadSettings_SpeedLimit_Title": "Limit Download Speed", - "FileDownloadSettings_SpeedLimit_NumBox": "Speed Limit (in MiB unit)", + "FileDownloadSettings_SpeedLimit_NumBox": "Speed Limit (in MiB)", "FileDownloadSettings_SpeedLimitHelp1": "Default:", "FileDownloadSettings_SpeedLimitHelp2": "Speed Limit value range:", "FileDownloadSettings_SpeedLimitHelp3": "1 - 1000 MiB/s", - "FileDownloadSettings_SpeedLimitHelp4": "Limiting the bandwidth for download. This setting cannot be used alongside", + "FileDownloadSettings_SpeedLimitHelp4": "Limits the maximum allowed bandwidth for downloading. This setting cannot be used alongside the", "FileDownloadSettings_SpeedLimitHelp5": "setting.", "FileDownloadSettings_NewPreallocChunk_Title": "New Pre-allocated Downloader", "FileDownloadSettings_NewPreallocChunk_Subtitle": "For Game Installation, Game Repair and Cache Updates only.", - "FileDownloadSettings_NewPreallocChunk_NumBox": "Chunk Size (in MiB unit)", + "FileDownloadSettings_NewPreallocChunk_NumBox": "Chunk Size (in MiB)", "FileDownloadSettings_NewPreallocChunkHelp1": "Default:", "FileDownloadSettings_NewPreallocChunkHelp2": "Chunk Size value range:", "FileDownloadSettings_NewPreallocChunkHelp3": "1 - 32 MiB", - "FileDownloadSettings_NewPreallocChunkHelp4": "When enabled, the downloader will dynamically pre-allocate the size of the file while downloading it.", + "FileDownloadSettings_NewPreallocChunkHelp4": "When enabled, the downloader will dynamically pre-allocate the file size during its download.", "FileDownloadSettings_NewPreallocChunkHelp5": "This enables the downloader to directly writes data chunks into the file without splitting it into separate files.", - "FileDownloadSettings_NewPreallocChunkHelp6": "When disabled, the launcher will use an old method where data chunks will be written into separate files. It will also disable", + "FileDownloadSettings_NewPreallocChunkHelp6": "When disabled, the launcher will use the old allocation method, where data chunks will be written into separate files. It will also disable the", "FileDownloadSettings_NewPreallocChunkHelp7": "and", "FileDownloadSettings_NewPreallocChunkHelp8": "settings.", "FileDownloadSettings_NewPreallocChunkHelp9": "Note:", - "FileDownloadSettings_NewPreallocChunkHelp10": "This feature is only available for Game Installation (including Initial Install, Update and Preload), Game Repair and Cache Update.", + "FileDownloadSettings_NewPreallocChunkHelp10": "This feature is only available for game installation (such as Initial Install, Update, and Pre-load), Game Repair, and Cache Update steps.", - "FileDownloadSettings_BurstDownload_Title": "Burst File-Download Mode", + "FileDownloadSettings_BurstDownload_Title": "Burst File Download Mode", "FileDownloadSettings_BurstDownload_Subtitle": "For Game Repair and Cache Updates only.", "FileDownloadSettings_BurstDownloadHelp1": "Default:", "FileDownloadSettings_BurstDownloadHelp2": "When enabled, this feature will allow the download process on", "FileDownloadSettings_BurstDownloadHelp3": "and", - "FileDownloadSettings_BurstDownloadHelp4": "feature to run in parallel at the same time to speed up the download process.", - "FileDownloadSettings_BurstDownloadHelp5": "When disabled, the download process on", + "FileDownloadSettings_BurstDownloadHelp4": "features to run in parallel to make the download process more efficient.", + "FileDownloadSettings_BurstDownloadHelp5": "When disabled, the download process for", "FileDownloadSettings_BurstDownloadHelp6": "and", - "FileDownloadSettings_BurstDownloadHelp7": "will use a sequential download process." + "FileDownloadSettings_BurstDownloadHelp7": "features will use a sequential download process." }, "_Misc": { From 9118fea258278ea64074cfcb3d9193e7a4bd602a Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 30 Aug 2024 23:54:58 +0700 Subject: [PATCH 21/25] Adjust reserved thread count as a const (pt. 2) --- .../Classes/CachesManagement/Honkai/Fetch.cs | 12 +++++------ .../Classes/CachesManagement/Honkai/Update.cs | 2 +- .../CachesManagement/StarRail/Fetch.cs | 2 +- .../CachesManagement/StarRail/Update.cs | 2 +- .../Interfaces/Class/GamePropertyBase.cs | 1 + .../RepairManagement/Honkai/HonkaiRepair.cs | 20 ++++++++----------- 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index 9b731a96a..c70224f92 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -25,11 +25,11 @@ internal partial class HonkaiCache private async Task> Fetch(CancellationToken token) { // Initialize asset index for the return - List returnAsset = new(); + List returnAsset = []; // Initialize new proxy-aware HttpClient using HttpClient httpClientNew = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); @@ -83,7 +83,7 @@ private async Task BuildGameRepoURL(DownloadClient downloadClient, CancellationT { try { - // Init the key and decrypt it if exist. + // Init the key and decrypt it if existed. if (string.IsNullOrEmpty(_gameVersionManager.GamePreset.DispatcherKey)) { throw new NullReferenceException("Dispatcher key is null or empty!"); @@ -114,7 +114,7 @@ private async Task BuildGameRepoURL(DownloadClient downloadClient, CancellationT _gameRepoURL = BuildAssetBundleURL(_gameGateway); } - private string BuildAssetBundleURL(KianaDispatch gateway) => CombineURLFromString(gateway!.AssetBundleUrls![0], "/{0}/editor_compressed/"); + private static string BuildAssetBundleURL(KianaDispatch gateway) => CombineURLFromString(gateway!.AssetBundleUrls![0], "/{0}/editor_compressed/"); private async Task<(int, long)> FetchByType(CacheAssetType type, DownloadClient downloadClient, List assetIndex, CancellationToken token) { @@ -137,7 +137,7 @@ private async Task BuildGameRepoURL(DownloadClient downloadClient, CancellationT await using HttpResponseInputStream remoteStream = await HttpResponseInputStream.CreateStreamAsync( downloadClient.GetHttpClient(), assetIndexURL, null, null, null, null, null, token); - using XORStream stream = new XORStream(remoteStream); + await using XORStream stream = new XORStream(remoteStream); // Build the asset index and return the count and size of each type (int, long) returnValue = await BuildAssetIndex(type, baseURL, stream, assetIndex, token); @@ -253,7 +253,7 @@ private IEnumerable EnumerateCacheTextAsset(CacheAssetType type, IEn // Initialize local HTTP client using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index 94e8db75d..b54a56456 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -21,7 +21,7 @@ private async Task Update(List updateAssetIndex, List() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs index bb612bcd9..863fece9e 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs @@ -26,7 +26,7 @@ private async Task> Fetch(CancellationToken token) // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index 636565d13..3ef249cce 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -24,7 +24,7 @@ private async Task Update(List updateAssetIndex, List as { // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() - .UseLauncherConfig(_downloadThreadCount + 16) + .UseLauncherConfig(_downloadThreadCount + _downloadThreadCountReserved) .SetUserAgent(_userAgent) .SetAllowedDecompression(DecompressionMethods.None) .Create(); diff --git a/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs b/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs index 94de5acf1..3247088ac 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs @@ -39,6 +39,7 @@ public GamePropertyBase(UIElement parentUI, IGameVersionCheck gameVersionManager protected const int _bufferMediumLength = 512 << 10; protected const int _bufferBigLength = 1 << 20; protected const int _sizeForMultiDownload = 10 << 20; + protected const int _downloadThreadCountReserved = 16; protected virtual string _userAgent { get; set; } = "UnityPlayer/2017.4.18f1 (UnityWebRequest/1.0, libcurl/7.51.0-DEV)"; protected bool _isVersionOverride { get; init; } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs index f597093be..546fbcd9f 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs @@ -43,22 +43,18 @@ internal partial class HonkaiRepair : ProgressBase, IRepai public HonkaiRepair(UIElement parentUI, IGameVersionCheck GameVersionManager, ICache GameCacheManager, IGameSettings GameSettings, bool onlyRecoverMainAsset = false, string versionOverride = null) : base(parentUI, GameVersionManager, GameSettings, null, "", versionOverride) { - _cacheUtil = (GameCacheManager as ICacheBase).AsBaseType(); + _cacheUtil = (GameCacheManager as ICacheBase)?.AsBaseType(); // Get flag to only recover main assets _isOnlyRecoverMain = onlyRecoverMainAsset; // Initialize audio asset language - string audioLanguage = (GameSettings as HonkaiSettings).SettingsAudio._userCVLanguage; - switch (audioLanguage) - { - case "Chinese(PRC)": - _audioLanguage = AudioLanguageType.Chinese; - break; - default: - _audioLanguage = _gameVersionManager.GamePreset.GameDefaultCVLanguage; - break; - } + string audioLanguage = (GameSettings as HonkaiSettings)?.SettingsAudio._userCVLanguage; + _audioLanguage = audioLanguage switch + { + "Chinese(PRC)" => AudioLanguageType.Chinese, + _ => _gameVersionManager.GamePreset.GameDefaultCVLanguage + }; } ~HonkaiRepair() => Dispose(); @@ -98,7 +94,7 @@ private async Task CheckRoutine() CountAssetIndex(_assetIndex); // Copy list to _originAssetIndex - _originAssetIndex = new List(_assetIndex); + _originAssetIndex = [.._assetIndex]; // Step 3: Check for the asset indexes integrity await Check(_assetIndex, _token.Token); From f4fc8be21615d52fb008ad6a5ac12f462938caee Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 31 Aug 2024 02:06:24 +0700 Subject: [PATCH 22/25] Code QA once again --- .../Classes/Interfaces/Class/ProgressBase.cs | 5 +- .../Classes/RepairManagement/Genshin/Fetch.cs | 216 +++++++------ .../GenshinDispatchHelper.cs | 305 +++++++++--------- .../ClassStruct/Class/RegionResource.cs | 27 +- Hi3Helper.Http | 2 +- 5 files changed, 288 insertions(+), 267 deletions(-) diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index f3b5aff28..152dd806b 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -16,6 +16,7 @@ using System.Buffers; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.IO.Hashing; @@ -538,10 +539,10 @@ protected async Task DoCopyStreamProgress(Stream source, Stream target, Cancella } } - protected string EnsureCreationOfDirectory(string str) + protected string EnsureCreationOfDirectory([DisallowNull] string str) { if (string.IsNullOrEmpty(str)) - return str; + ArgumentException.ThrowIfNullOrEmpty(str); string dir = Path.GetDirectoryName(str); if (!Directory.Exists(dir)) diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index f9ed70e24..ad6095bc5 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -17,6 +17,10 @@ using static Hi3Helper.Data.ConverterTool; using static Hi3Helper.Locale; using static Hi3Helper.Logger; +// ReSharper disable IdentifierTypo +// ReSharper disable CheckNamespace +// ReSharper disable CommentTypo +// ReSharper disable StringLiteralTypo namespace CollapseLauncher { @@ -30,7 +34,7 @@ private async ValueTask> Fetch(List hashtableManifest = new Dictionary(); + Dictionary hashtableManifest = new(); // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() @@ -65,11 +69,11 @@ private async ValueTask> Fetch(List assetIndex) { - _gameVersionManager.GameAPIProp.data.plugins?.ForEach(plugin => + _gameVersionManager.GameAPIProp.data?.plugins?.ForEach(plugin => { assetIndex.RemoveAll(asset => { - return plugin.package.validate?.Exists(validate => validate.path == asset.remoteName) ?? false; + return plugin.package?.validate?.Exists(validate => validate.path == asset.remoteName) ?? false; }); }); } @@ -81,7 +85,7 @@ private List EliminateUnnecessaryAssetIndex(IEnumerable ignoredAudioLangList = audioLangList.Where(x => !currentAudioLangList.Contains(x)).ToList(); @@ -99,7 +103,7 @@ private async Task BuildPrimaryManifest(DownloadClient downloadClient, DownloadP TryDeleteDownloadPref(); // Build basic file entry. - string ManifestPath = Path.Combine(_gamePath, "pkg_version"); + string manifestPath = Path.Combine(_gamePath, "pkg_version"); // Download basic package version list var basicVerURL = CombineURLFromString(_gameRepoURL, "pkg_version"); @@ -108,7 +112,7 @@ private async Task BuildPrimaryManifest(DownloadClient downloadClient, DownloadP #endif await downloadClient.DownloadAsync( basicVerURL, - EnsureCreationOfDirectory(ManifestPath), + EnsureCreationOfDirectory(manifestPath), true, progressDelegateAsync: downloadProgress, maxConnectionSessions: _downloadThreadCount, @@ -173,7 +177,7 @@ await downloadClient.DownloadAsync( ); // Parse basic package version. - ParseManifestToAssetIndex(ManifestPath, assetIndex, hashtableManifest, "", "", _gameRepoURL, true); + ParseManifestToAssetIndex(manifestPath, assetIndex, hashtableManifest, "", "", _gameRepoURL, true); // Build additional blks entry. var streamingAssetsPath = $"{_execPrefix}_Data\\StreamingAssets"; @@ -217,7 +221,7 @@ private async Task BuildPersistentManifest(DownloadClient downloadClient, try { // Get the Dispatcher Query - QueryProperty queryProperty = await GetDispatcherQuery(token); + QueryProperty queryProperty = await GetDispatcherQuery(downloadClient.GetHttpClient(), token); // Initialize persistent folder path and check for the folder existence string basePersistentPath = $"{_execPrefix}_Data\\Persistent"; @@ -325,139 +329,137 @@ private void ParsePersistentManifest(string localManifestPath, streamingAssetPath = streamingAssetPath.Replace('\\', '/'); // Start reading the manifest - using (StreamReader reader = new StreamReader(localManifestPath, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read })) + using StreamReader reader = new StreamReader(localManifestPath, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read }); + while (!reader.EndOfStream) { - while (!reader.EndOfStream) + string manifestLine = reader.ReadLine(); + PkgVersionProperties manifestEntry = manifestLine.Deserialize(CoreLibraryJSONContext.Default); + + // Ignore if the remote name is "svc_catalog" or "ctable.dat" + if (Path.GetFileName(manifestEntry.remoteName).Equals("svc_catalog", StringComparison.OrdinalIgnoreCase) || + Path.GetFileName(manifestEntry.remoteName).Equals("ctable.dat", StringComparison.OrdinalIgnoreCase)) continue; + + // Get relative path based on extension + string relativePath = Path.GetExtension(manifestEntry.remoteName).ToLower() switch + { + ".pck" => "AudioAssets", + ".blk" => "AssetBundles", + ".usm" => "VideoAssets", + ".cuepoint" => "VideoAssets", + _ => "" + }; + + string actualName = string.IsNullOrEmpty(manifestEntry.localName) ? manifestEntry.remoteName : manifestEntry.localName; + string assetPersistentPath = relativePath == "" ? null : CombineURLFromString(persistentPath, relativePath, actualName); + string assetStreamingAssetPath = CombineURLFromString(streamingAssetPath, relativePath, manifestEntry.remoteName); + + // Set the remote URL + string remoteURL; + if (!string.IsNullOrEmpty(secondaryParentURL) && !manifestEntry.isPatch) { - string manifestLine = reader.ReadLine(); - PkgVersionProperties manifestEntry = manifestLine.Deserialize(CoreLibraryJSONContext.Default); - - // Ignore if the remote name is "svc_catalog" or "ctable.dat" - if (Path.GetFileName(manifestEntry.remoteName).Equals("svc_catalog", StringComparison.OrdinalIgnoreCase) || - Path.GetFileName(manifestEntry.remoteName).Equals("ctable.dat", StringComparison.OrdinalIgnoreCase)) continue; - - // Get relative path based on extension - string relativePath = Path.GetExtension(manifestEntry.remoteName).ToLower() switch - { - ".pck" => "AudioAssets", - ".blk" => "AssetBundles", - ".usm" => "VideoAssets", - ".cuepoint" => "VideoAssets", - _ => "" - }; - - string actualName = string.IsNullOrEmpty(manifestEntry.localName) ? manifestEntry.remoteName : manifestEntry.localName; - string assetPersistentPath = relativePath == "" ? null : CombineURLFromString(persistentPath, relativePath, actualName); - string assetStreamingAssetPath = CombineURLFromString(streamingAssetPath, relativePath, manifestEntry.remoteName); - - // Set the remote URL - string remoteURL; - if (!string.IsNullOrEmpty(secondaryParentURL) && !manifestEntry.isPatch) - { - remoteURL = CombineURLFromString(secondaryParentURL, relativePath, manifestEntry.remoteName); - } - else - { - remoteURL = CombineURLFromString(primaryParentURL, relativePath, manifestEntry.remoteName); - } - - // Get the remoteName (StreamingAssets) and remoteNamePersistent (Persistent) - manifestEntry.remoteURL = remoteURL; - manifestEntry.remoteName = assetStreamingAssetPath; - manifestEntry.remoteNamePersistent = assetPersistentPath; - // Decide if the file is forced to be in persistent or not - manifestEntry.isForceStoreInPersistent = forceStoreInPersistent || manifestEntry.isPatch; - - // If forceOverwrite and forceStoreInPwrsistent is true, then - // make it as a patch file and store it to persistent - if (forceOverwrite && forceStoreInPersistent) - { - manifestEntry.isForceStoreInStreaming = false; - manifestEntry.isForceStoreInPersistent = true; - manifestEntry.isPatch = true; - } - - // If the manifest has isPatch set to true, then set force store in streaming to false - if (manifestEntry.isPatch) manifestEntry.isForceStoreInStreaming = false; - - // Check if the hashtable has the value - bool IsHashHasValue = hashtable.ContainsKey(assetStreamingAssetPath); - if (IsHashHasValue) - { - // If yes, then get the reference and index ID - PkgVersionProperties reference = hashtable[assetStreamingAssetPath]; - int indexID = assetIndex.IndexOf(reference); - - // If the index is not found (== -1), then skip it. - // Otherwise, continue overriding its value - if (indexID == -1) continue; - - // Override the force state if isPatch is true - manifestEntry.isForceStoreInStreaming = !manifestEntry.isPatch; - - // If it has isForceStoreStreamingAssets flag and isPatch is false, then continue. - if (hashtable[assetStreamingAssetPath].isForceStoreInStreaming - && !manifestEntry.isPatch) continue; - - // Start overriding the value - hashtable[assetStreamingAssetPath] = manifestEntry; - assetIndex[indexID] = manifestEntry; - } - else - { - manifestEntry.isForceStoreInStreaming = !manifestEntry.isPatch; - - hashtable.Add(manifestEntry.remoteName, manifestEntry); - assetIndex.Add(manifestEntry); - } + remoteURL = CombineURLFromString(secondaryParentURL, relativePath, manifestEntry.remoteName); + } + else + { + remoteURL = CombineURLFromString(primaryParentURL, relativePath, manifestEntry.remoteName); + } + + // Get the remoteName (StreamingAssets) and remoteNamePersistent (Persistent) + manifestEntry.remoteURL = remoteURL; + manifestEntry.remoteName = assetStreamingAssetPath; + manifestEntry.remoteNamePersistent = assetPersistentPath; + // Decide if the file is forced to be in persistent or not + manifestEntry.isForceStoreInPersistent = forceStoreInPersistent || manifestEntry.isPatch; + + // If forceOverwrite and forceStoreInPersistent is true, then + // make it as a patch file and store it to persistent + if (forceOverwrite && forceStoreInPersistent) + { + manifestEntry.isForceStoreInStreaming = false; + manifestEntry.isForceStoreInPersistent = true; + manifestEntry.isPatch = true; + } + + // If the manifest has isPatch set to true, then set force store in streaming to false + if (manifestEntry.isPatch) manifestEntry.isForceStoreInStreaming = false; + + // Check if the hashtable has the value + bool isHashHasValue = hashtable.ContainsKey(assetStreamingAssetPath); + if (isHashHasValue) + { + // If yes, then get the reference and index ID + PkgVersionProperties reference = hashtable[assetStreamingAssetPath]; + int indexID = assetIndex.IndexOf(reference); + + // If the index is not found (== -1), then skip it. + // Otherwise, continue overriding its value + if (indexID == -1) continue; + + // Override the force state if isPatch is true + manifestEntry.isForceStoreInStreaming = !manifestEntry.isPatch; + + // If it has isForceStoreStreamingAssets flag and isPatch is false, then continue. + if (hashtable[assetStreamingAssetPath].isForceStoreInStreaming + && !manifestEntry.isPatch) continue; + + // Start overriding the value + hashtable[assetStreamingAssetPath] = manifestEntry; + assetIndex[indexID] = manifestEntry; + } + else + { + manifestEntry.isForceStoreInStreaming = !manifestEntry.isPatch; + + hashtable.Add(manifestEntry.remoteName, manifestEntry); + assetIndex.Add(manifestEntry); } } } private void SavePersistentRevision(QueryProperty dispatchQuery) { - string PersistentPath = Path.Combine(_gamePath, $"{_execPrefix}_Data\\Persistent"); + string persistentPath = Path.Combine(_gamePath, $"{_execPrefix}_Data\\Persistent"); // Get base_res_version_hash content - string FilePath = Path.Combine(_gamePath, $"{_execPrefix}_Data\\StreamingAssets\\res_versions_streaming"); - string Hash = CreateMD5Shared(new FileStream(FilePath, FileMode.Open, FileAccess.Read)); + string filePath = Path.Combine(_gamePath, $@"{_execPrefix}_Data\StreamingAssets\res_versions_streaming"); + string hash = CreateMD5Shared(new FileStream(filePath, FileMode.Open, FileAccess.Read)); #nullable enable // Write DownloadPref template - byte[]? PrefTemplateBytes = (_gameVersionManager as GameTypeGenshinVersion)?.GamePreset + byte[]? prefTemplateBytes = (_gameVersionManager as GameTypeGenshinVersion)?.GamePreset .GetGameDataTemplate("DownloadPref", _gameVersion.VersionArrayManifest.Select(x => (byte)x).ToArray()); - if (PrefTemplateBytes != null) File.WriteAllBytes(PersistentPath + "\\DownloadPref", PrefTemplateBytes); + if (prefTemplateBytes != null) File.WriteAllBytes(persistentPath + "\\DownloadPref", prefTemplateBytes); #nullable disable // Get base_res_version_hash content - File.WriteAllText(PersistentPath + "\\base_res_version_hash", Hash); + File.WriteAllText(persistentPath + "\\base_res_version_hash", hash); // Get data_revision content - File.WriteAllText(PersistentPath + "\\data_revision", $"{dispatchQuery.DataRevisionNum}"); + File.WriteAllText(persistentPath + "\\data_revision", $"{dispatchQuery.DataRevisionNum}"); // Get res_revision content - File.WriteAllText(PersistentPath + "\\res_revision", $"{dispatchQuery.ResRevisionNum}"); + File.WriteAllText(persistentPath + "\\res_revision", $"{dispatchQuery.ResRevisionNum}"); // Get silence_revision content - File.WriteAllText(PersistentPath + "\\silence_revision", $"{dispatchQuery.SilenceRevisionNum}"); + File.WriteAllText(persistentPath + "\\silence_revision", $"{dispatchQuery.SilenceRevisionNum}"); // Get audio_revision content - File.WriteAllText(PersistentPath + "\\audio_revision", $"{dispatchQuery.AudioRevisionNum}"); + File.WriteAllText(persistentPath + "\\audio_revision", $"{dispatchQuery.AudioRevisionNum}"); // Get ChannelName content - File.WriteAllText(PersistentPath + "\\ChannelName", $"{dispatchQuery.ChannelName}"); + File.WriteAllText(persistentPath + "\\ChannelName", $"{dispatchQuery.ChannelName}"); // Get ScriptVersion content - File.WriteAllText(PersistentPath + "\\ScriptVersion", $"{dispatchQuery.GameVersion}"); + File.WriteAllText(persistentPath + "\\ScriptVersion", $"{dispatchQuery.GameVersion}"); } #endregion #region DispatcherParser // ReSharper disable once UnusedParameter.Local - private async Task GetDispatcherQuery(CancellationToken token) + private async Task GetDispatcherQuery(HttpClient client, CancellationToken token) { // Initialize dispatch helper - using (GenshinDispatchHelper dispatchHelper = new GenshinDispatchHelper(_dispatcherRegionID, _gameVersionManager.GamePreset.ProtoDispatchKey, _dispatcherURL, _gameVersion.VersionString, token)) + GenshinDispatchHelper dispatchHelper = new GenshinDispatchHelper(client, _dispatcherRegionID, _gameVersionManager.GamePreset.ProtoDispatchKey!, _dispatcherURL, _gameVersion.VersionString, token); { // Get the dispatcher info YSDispatchInfo dispatchInfo = await dispatchHelper.LoadDispatchInfo(); // DEBUG ONLY: Show encrypted Proto as JSON+Base64 format - string dFormat = string.Format("Query Response (RAW Encrypted form):\r\n{0}", dispatchInfo.content); + string dFormat = $"Query Response (RAW Encrypted form):\r\n{dispatchInfo?.content}"; #if DEBUG LogWriteLine(dFormat); #endif @@ -477,7 +479,7 @@ private async Task TryDecryptAndParseDispatcher(YSDispatchInfo di byte[] decryptedData = dispatchDecryptor.DecryptYSDispatch(dispatchInfo.content, _gameVersionManager.GamePreset.DispatcherKeyBitLength ?? 0, _gameVersionManager.GamePreset.DispatcherKey); // DEBUG ONLY: Show the decrypted Proto as Base64 format - string dFormat = string.Format("Proto Response (RAW Decrypted form):\r\n{0}", Convert.ToBase64String(decryptedData)); + string dFormat = $"Proto Response (RAW Decrypted form):\r\n{Convert.ToBase64String(decryptedData)}"; #if DEBUG LogWriteLine(dFormat); #endif diff --git a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs index 7999a11e3..24ec6a515 100644 --- a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs +++ b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs @@ -1,172 +1,187 @@ -using Hi3Helper.EncTool.Parser.AssetIndex; -using Hi3Helper.EncTool.Proto.Genshin; -using Hi3Helper.Shared.ClassStruct; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Hi3Helper.Data -{ - public class GenshinDispatchHelper : IDisposable +#nullable enable + using Hi3Helper.EncTool.Parser.AssetIndex; + using Hi3Helper.EncTool.Proto.Genshin; + using Hi3Helper.Shared.ClassStruct; + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Net.Http.Json; + using System.Text; + using System.Text.Json; + using System.Threading; + using System.Threading.Tasks; + + // ReSharper disable IdentifierTypo + // ReSharper disable CheckNamespace + // ReSharper disable StringLiteralTypo + // ReSharper disable CommentTypo + + namespace Hi3Helper.Data { - private Http.Legacy.Http _httpClient; - private string DispatchBaseURL { get; set; } - private string RegionSubdomain { get; set; } - private string ChannelName = "OSRELWin"; - private string Version { get; set; } + public class GenshinDispatchHelper + { + private readonly CancellationToken _cancelToken; + private readonly string _channelName = "OSRELWin"; - private GenshinGateway Gateway; - private QueryProperty returnValProp; - private CancellationToken cancelToken; + private GenshinGateway? _gateway; + private readonly HttpClient _httpClient; + private QueryProperty? _returnValProp; - public GenshinDispatchHelper(int RegionID, string DispatchKey, string DispatchURLPrefix, string VersionString = "2.6.0", CancellationToken cancelToken = new CancellationToken()) - { - if (RegionID >= 4) ChannelName = "CNRELWin"; - this._httpClient = new Http.Legacy.Http(false, 1, 1); - this.RegionSubdomain = GetSubdomainByRegionID(RegionID); - this.Version = VersionString; - this.DispatchBaseURL = string.Format(DispatchURLPrefix!, RegionSubdomain, $"{ChannelName}{VersionString}", DispatchKey); - this.cancelToken = cancelToken; - } + public GenshinDispatchHelper(HttpClient httpClient, int regionID, string dispatchKey, string dispatchURLPrefix, + string versionString = "2.6.0", CancellationToken cancelToken = new()) + { + if (regionID >= 4) + { + _channelName = "CNRELWin"; + } - ~GenshinDispatchHelper() => Dispose(); + _httpClient = httpClient; + RegionSubdomain = GetSubdomainByRegionID(regionID); + Version = versionString; + DispatchBaseURL = string.Format(dispatchURLPrefix, RegionSubdomain, $"{_channelName}{versionString}", + dispatchKey); + _cancelToken = cancelToken; + } - public void Dispose() => this._httpClient?.Dispose(); + private string DispatchBaseURL { get; } + private string RegionSubdomain { get; } + private string Version { get; } - public async Task LoadDispatchInfo() - { - YSDispatchInfo DispatcherDataInfo; - using (MemoryStream s = new MemoryStream()) + public async Task LoadDispatchInfo() { -#if DEBUG + #if DEBUG // DEBUG ONLY: Show URL of Proto - string dFormat = string.Format("URL for Proto Response:\r\n{0}", DispatchBaseURL); - Console.WriteLine(dFormat); - Logger.WriteLog(dFormat, LogType.Default); -#endif - await this._httpClient!.Download(DispatchBaseURL, s, null, null, cancelToken); - s.Position = 0; - DispatcherDataInfo = (YSDispatchInfo)JsonSerializer.Deserialize(s, typeof(YSDispatchInfo), CoreLibraryJSONContext.Default); - } + string dFormat = $"URL for Proto Response:\r\n{DispatchBaseURL}"; + Logger.LogWriteLine(dFormat, LogType.Default, true); + #endif - return DispatcherDataInfo; - } + return await _httpClient.GetFromJsonAsync(DispatchBaseURL, CoreLibraryJSONContext.Default.YSDispatchInfo, + _cancelToken); + } - public async Task LoadDispatch(byte[] CustomDispatchData = null) - { - Gateway = GenshinGateway.Parser!.ParseFrom(CustomDispatchData); - returnValProp = new QueryProperty() + public async Task LoadDispatch(byte[] customDispatchData) { - GameServerName = Gateway!.GatewayProperties!.ServerName, - ClientGameResURL = string.Format("{0}/output_{1}_{2}/client", - Gateway.GatewayProperties.RepoResVersionURL, - Gateway.GatewayProperties.RepoResVersionProperties!.ResVersionNumber, - Gateway.GatewayProperties.RepoResVersionProperties.ResVersionHash), - ClientDesignDataURL = string.Format("{0}/output_{1}_{2}/client/General", - Gateway.GatewayProperties.RepoDesignDataURL, - Gateway.GatewayProperties.RepoDesignDataNumber, - Gateway.GatewayProperties.RepoDesignDataHash), - ClientDesignDataSilURL = string.Format("{0}/output_{1}_{2}/client_silence/General", - Gateway.GatewayProperties.RepoDesignDataURL, - Gateway.GatewayProperties.RepoDesignDataSilenceNumber, - Gateway.GatewayProperties.RepoDesignDataSilenceHash), - DataRevisionNum = Gateway.GatewayProperties.RepoDesignDataNumber, - SilenceRevisionNum = Gateway.GatewayProperties.RepoDesignDataSilenceNumber, - ResRevisionNum = Gateway.GatewayProperties.RepoResVersionProperties.ResVersionNumber, - ChannelName = this.ChannelName, - GameVersion = this.Version - }; - - ParseGameResPkgProp(ref returnValProp); - ParseDesignDataURL(ref returnValProp); - await ParseAudioAssetsURL(returnValProp); - } + _gateway = GenshinGateway.Parser!.ParseFrom(customDispatchData); + _returnValProp = new QueryProperty + { + GameServerName = _gateway!.GatewayProperties!.ServerName, + ClientGameResURL = + $"{_gateway.GatewayProperties.RepoResVersionURL}/output_{_gateway.GatewayProperties.RepoResVersionProperties!.ResVersionNumber}_{_gateway.GatewayProperties.RepoResVersionProperties.ResVersionHash}/client", + ClientDesignDataURL = + $"{_gateway.GatewayProperties.RepoDesignDataURL}/output_{_gateway.GatewayProperties.RepoDesignDataNumber}_{_gateway.GatewayProperties.RepoDesignDataHash}/client/General", + ClientDesignDataSilURL = + $"{_gateway.GatewayProperties.RepoDesignDataURL}/output_{_gateway.GatewayProperties.RepoDesignDataSilenceNumber}_{_gateway.GatewayProperties.RepoDesignDataSilenceHash}/client_silence/General", + DataRevisionNum = _gateway.GatewayProperties.RepoDesignDataNumber, + SilenceRevisionNum = _gateway.GatewayProperties.RepoDesignDataSilenceNumber, + ResRevisionNum = _gateway.GatewayProperties.RepoResVersionProperties.ResVersionNumber, + ChannelName = _channelName, + GameVersion = Version + }; + + ParseGameResPkgProp(_returnValProp); + ParseDesignDataURL(_returnValProp); + await ParseAudioAssetsURL(_returnValProp); + } - private void ParseDesignDataURL(ref QueryProperty ValProp) - { - string[] DataList = Gateway!.GatewayProperties!.RepoResVersionProperties!.ResVersionMapJSON!.Split("\r\n"); - ValProp!.ClientGameRes = new List(); - foreach (string Data in DataList) + private void ParseDesignDataURL(QueryProperty valProp) { - (ValProp.ClientGameRes as List)! - .Add( - (PkgVersionProperties)JsonSerializer.Deserialize(Data, typeof(PkgVersionProperties), CoreLibraryJSONContext.Default) - ); + string[] dataList = _gateway!.GatewayProperties!.RepoResVersionProperties!.ResVersionMapJSON!.Split("\r\n"); + valProp.ClientGameRes = new List(); + foreach (string data in dataList) + { + (valProp.ClientGameRes as List)? + .Add( + (PkgVersionProperties?)JsonSerializer.Deserialize(data, typeof(PkgVersionProperties), + CoreLibraryJSONContext.Default) + ); + } } - } - private void ParseGameResPkgProp(ref QueryProperty ValProp) - { - var jsonDesignData = Gateway!.GatewayProperties!.RepoDesignDataJSON; - var jsonDesignDataSil = Gateway!.GatewayProperties!.RepoDesignDataSilenceJSON; + private void ParseGameResPkgProp(QueryProperty valProp) + { + var jsonDesignData = _gateway!.GatewayProperties!.RepoDesignDataJSON; + var jsonDesignDataSil = _gateway!.GatewayProperties!.RepoDesignDataSilenceJSON; #if DEBUG - Logger.LogWriteLine($"[GenshinDispatchHelper::ParseGameResPkgProp] DesignData Response:" + - $"\r\n\tDesignData:\r\n{jsonDesignData}" + - $"\r\n\tDesignData_Silence:\r\n{jsonDesignDataSil}", LogType.Debug, true); + Logger.LogWriteLine($"[GenshinDispatchHelper::ParseGameResPkgProp] DesignData Response:" + + $"\r\n\tDesignData:\r\n{jsonDesignData}" + + $"\r\n\tDesignData_Silence:\r\n{jsonDesignDataSil}", LogType.Debug, true); #endif - if (!string.IsNullOrEmpty(jsonDesignData)) - { - string[] designDataArr = jsonDesignData.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + if (!string.IsNullOrEmpty(jsonDesignData)) + { + string[] designDataArr = jsonDesignData.Split(["\r\n", "\r", "\n"], StringSplitOptions.None); + + foreach (string designData in designDataArr) + { + var designDataSer = + JsonSerializer.Deserialize(designData, CoreLibraryJSONContext.Default.PkgVersionProperties); + // Only serialize data_versions + if (designDataSer is { remoteName: "data_versions" }) + { + valProp.ClientDesignData = designDataSer; + } + + if (designDataSer == null) + { + Logger.LogWriteLine("[GenshinDispatchHelper::ParseGameResPkgProp] DesignData is null!", + LogType.Warning, true); + } + } + } - foreach (string designData in designDataArr) + if (jsonDesignDataSil != null) { - var designDataSer = (PkgVersionProperties)JsonSerializer.Deserialize(designData, - typeof(PkgVersionProperties), CoreLibraryJSONContext.Default); - // Only serialize data_versions - if (designDataSer != null && designDataSer.remoteName == "data_versions") - ValProp!.ClientDesignData = designDataSer; - if (designDataSer == null) - Logger.LogWriteLine("[GenshinDispatchHelper::ParseGameResPkgProp] DesignData is null!", LogType.Warning, true); + valProp.ClientDesignDataSil = + JsonSerializer.Deserialize(jsonDesignDataSil, typeof(PkgVersionProperties), + CoreLibraryJSONContext.Default) as PkgVersionProperties; + } + else + { + Logger.LogWriteLine("[GenshinDispatchHelper::ParseGameResPkgProp] DesignData_Silence is null!", + LogType.Warning, true); } } - if (jsonDesignDataSil != null) - ValProp!.ClientDesignDataSil = - (PkgVersionProperties)JsonSerializer.Deserialize(jsonDesignDataSil, typeof(PkgVersionProperties), - CoreLibraryJSONContext.Default); - else Logger.LogWriteLine("[GenshinDispatchHelper::ParseGameResPkgProp] DesignData_Silence is null!", LogType.Warning, true); - } - - private async Task ParseAudioAssetsURL(QueryProperty ValProp) - { - using (MemoryStream response = new MemoryStream()) + private async Task ParseAudioAssetsURL(QueryProperty valProp) { - await this._httpClient!.Download(ConverterTool.CombineURLFromString(ValProp!.ClientGameResURL, "/StandaloneWindows64/base_revision"), response, null, null, cancelToken); - string[] responseData = Encoding.UTF8.GetString(response.ToArray()).Split(' '); - - ValProp.ClientAudioAssetsURL = string.Format("{0}/output_{1}_{2}/client", - Gateway!.GatewayProperties!.RepoResVersionURL, - responseData[0], - responseData[1]); - ValProp.AudioRevisionNum = uint.Parse(responseData[0]); + byte[] byteData = await _httpClient + .GetByteArrayAsync( + ConverterTool.CombineURLFromString(valProp.ClientGameResURL, + "/StandaloneWindows64/base_revision"), + _cancelToken); + string[] responseData = Encoding.UTF8.GetString(byteData).Split(' '); + + valProp.ClientAudioAssetsURL = + $"{_gateway!.GatewayProperties!.RepoResVersionURL}/output_{responseData[0]}_{responseData[1]}/client"; + valProp.AudioRevisionNum = uint.Parse(responseData[0]); } - } - public QueryProperty GetResult() => returnValProp; + public QueryProperty? GetResult() + { + return _returnValProp; + } - private string GetSubdomainByRegionID(int RegionID) => RegionID switch - { - /* - * Region ID: - * 0 = USA - * 1 = Europe - * 2 = Asia - * 3 = TW/HK/MO - * 4 = Mainland China - * 5 = Mainland China (Bilibili) - */ - 0 => "osusadispatch", - 1 => "oseurodispatch", - 2 => "osasiadispatch", - 3 => "oschtdispatch", - 4 => "cngfdispatch", - 5 => "cnqddispatch", - _ => throw new FormatException("Unknown region ID!") - }; - } -} + private static string GetSubdomainByRegionID(int regionID) + { + return regionID switch + { + /* + * Region ID: + * 0 = USA + * 1 = Europe + * 2 = Asia + * 3 = TW/HK/MO + * 4 = Mainland China + * 5 = Mainland China (Bilibili) + */ + 0 => "osusadispatch", + 1 => "oseurodispatch", + 2 => "osasiadispatch", + 3 => "oschtdispatch", + 4 => "cngfdispatch", + 5 => "cnqddispatch", + _ => throw new FormatException("Unknown region ID!") + }; + } + } + } \ No newline at end of file diff --git a/Hi3Helper.Core/Classes/Shared/ClassStruct/Class/RegionResource.cs b/Hi3Helper.Core/Classes/Shared/ClassStruct/Class/RegionResource.cs index 702794f0b..24cab964a 100644 --- a/Hi3Helper.Core/Classes/Shared/ClassStruct/Class/RegionResource.cs +++ b/Hi3Helper.Core/Classes/Shared/ClassStruct/Class/RegionResource.cs @@ -1,29 +1,32 @@ using Hi3Helper.EncTool.Parser.AssetIndex; using System.Collections.Generic; +// ReSharper disable InconsistentNaming +// ReSharper disable CheckNamespace +#nullable enable namespace Hi3Helper.Shared.ClassStruct { public class YSDispatchInfo { - public string content { get; set; } - public string sign { get; set; } + public string? content { get; set; } + public string? sign { get; set; } } public class QueryProperty { - public string GameServerName { get; set; } - public string ClientGameResURL { get; set; } - public string ClientDesignDataURL { get; set; } - public string ClientDesignDataSilURL { get; set; } - public string ClientAudioAssetsURL { get; set; } + public string? GameServerName { get; set; } + public string? ClientGameResURL { get; set; } + public string? ClientDesignDataURL { get; set; } + public string? ClientDesignDataSilURL { get; set; } + public string? ClientAudioAssetsURL { get; set; } public uint AudioRevisionNum { get; set; } public uint DataRevisionNum { get; set; } public uint ResRevisionNum { get; set; } public uint SilenceRevisionNum { get; set; } - public string GameVersion { get; set; } - public string ChannelName { get; set; } - public IEnumerable ClientGameRes { get; set; } - public PkgVersionProperties ClientDesignData { get; set; } - public PkgVersionProperties ClientDesignDataSil { get; set; } + public string? GameVersion { get; set; } + public string? ChannelName { get; set; } + public IEnumerable? ClientGameRes { get; set; } + public PkgVersionProperties? ClientDesignData { get; set; } + public PkgVersionProperties? ClientDesignDataSil { get; set; } } } diff --git a/Hi3Helper.Http b/Hi3Helper.Http index 2c6e2a52d..bc1709f39 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit 2c6e2a52d16b0c0d8a57a6c1a8f498d8b67cc14e +Subproject commit bc1709f3996327c6251c3c27e9e275c20283e613 From 193033f8be2be8a36165f867659dabddc56c24c6 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 31 Aug 2024 10:41:06 +0700 Subject: [PATCH 23/25] Adding speed limiter to Sophon --- .../BaseClass/InstallManagerBase.cs | 56 +++++++++++++------ CollapseLauncher/CollapseLauncher.csproj | 3 +- Hi3Helper.EncTool | 2 +- Hi3Helper.Sophon | 2 +- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 2c385d5e1..e9a996276 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -1006,12 +1006,24 @@ async Task RunTaskAction(HttpClient client, List so ParallelOptions parallelOptions, Func actionDelegate) { - foreach (SophonChunkManifestInfoPair sophonDownloadInfoPair in sophonInfoPairList) + // Create a sophon download speed limiter instance + SophonDownloadSpeedLimiter downloadSpeedLimiter = SophonDownloadSpeedLimiter.CreateInstance(LauncherConfig.DownloadSpeedLimitCached); + + try + { + LauncherConfig.DownloadSpeedLimitChanged += downloadSpeedLimiter.GetListener(); + foreach (SophonChunkManifestInfoPair sophonDownloadInfoPair in sophonInfoPairList) + { + // Enumerate in parallel and process the assets + await Parallel.ForEachAsync(SophonManifest.EnumerateAsync(client, sophonDownloadInfoPair, + downloadSpeedLimiter), + parallelOptions, + actionDelegate); + } + } + finally { - // Enumerate in parallel and process the assets - await Parallel.ForEachAsync(SophonManifest.EnumerateAsync(client, sophonDownloadInfoPair), - parallelOptions, - actionDelegate); + LauncherConfig.DownloadSpeedLimitChanged -= downloadSpeedLimiter.GetListener(); } } } @@ -1081,16 +1093,20 @@ await _gameVersionManager.GamePreset // Add the tag query to the previous version's Url requestedBaseUrlFrom += $"&tag={requestedVersionFrom.ToString()}"; + // Create a sophon download speed limiter instance + SophonDownloadSpeedLimiter downloadSpeedLimiter = SophonDownloadSpeedLimiter.CreateInstance(LauncherConfig.DownloadSpeedLimitCached); + // Add base game diff data await AddSophonDiffAssetsToList(httpClient, requestedBaseUrlFrom, requestedBaseUrlTo, - sophonUpdateAssetList, "game"); + sophonUpdateAssetList, "game", downloadSpeedLimiter); // If the game has lang list path, then add it if (_gameAudioLangListPath != null) { // Add existing voice-over diff data await AddSophonAdditionalVODiffAssetsToList(httpClient, requestedBaseUrlFrom, - requestedBaseUrlTo, sophonUpdateAssetList); + requestedBaseUrlTo, sophonUpdateAssetList, + downloadSpeedLimiter); } } @@ -1211,10 +1227,12 @@ protected virtual void CleanupTempSophonVerifiedFiles() } } - private async Task AddSophonDiffAssetsToList(HttpClient httpClient, - string requestedUrlFrom, string requestedUrlTo, - List sophonPreloadAssetList, - string matchingField) + private async Task AddSophonDiffAssetsToList(HttpClient httpClient, + string requestedUrlFrom, + string requestedUrlTo, + List sophonPreloadAssetList, + string matchingField, + SophonDownloadSpeedLimiter downloadSpeedLimiter) { // Get the manifest pair for both previous (from) and next (to) version SophonChunkManifestInfoPair requestPairFrom = await SophonManifest @@ -1225,22 +1243,24 @@ private async Task AddSophonDiffAssetsToList(HttpClient httpClient, // Add asset to the list await foreach (SophonAsset sophonAsset in SophonUpdate .EnumerateUpdateAsync(httpClient, requestPairFrom, requestPairTo, - false) + false, downloadSpeedLimiter) .WithCancellation(_token.Token)) { sophonPreloadAssetList.Add(sophonAsset); } } - private async Task AddSophonAdditionalVODiffAssetsToList(HttpClient httpClient, - string requestedUrlFrom, string requestedUrlTo, - List sophonPreloadAssetList) + private async Task AddSophonAdditionalVODiffAssetsToList(HttpClient httpClient, + string requestedUrlFrom, + string requestedUrlTo, + List sophonPreloadAssetList, + SophonDownloadSpeedLimiter downloadSpeedLimiter) { // Get the main VO language name from Id string mainLangId = GetLanguageLocaleCodeByID(_gameVoiceLanguageID); // Get the manifest pair for both previous (from) and next (to) version for the main VO - await AddSophonDiffAssetsToList(httpClient, requestedUrlFrom, requestedUrlTo, sophonPreloadAssetList, - mainLangId); + await AddSophonDiffAssetsToList(httpClient, requestedUrlFrom, requestedUrlTo, + sophonPreloadAssetList, mainLangId, downloadSpeedLimiter); // Check if the audio lang list file is exist, then try add others if (File.Exists(_gameAudioLangListPath)) @@ -1261,7 +1281,7 @@ await AddSophonDiffAssetsToList(httpClient, requestedUrlFrom, requestedUrlTo, so string otherLangId = GetLanguageLocaleCodeByLanguageString(line); // Get the manifest pair for both previous (from) and next (to) version for other VOs await AddSophonDiffAssetsToList(httpClient, requestedUrlFrom, requestedUrlTo, - sophonPreloadAssetList, otherLangId); + sophonPreloadAssetList, otherLangId, downloadSpeedLimiter); } } } diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index 8a7cca71e..1b8a00524 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -92,12 +92,13 @@ - + + diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index 4fdf1e971..41e2df06b 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit 4fdf1e971946421fbb1b767ed41bcaff0201b5e6 +Subproject commit 41e2df06b7ef783731d29d218e122da09a98211a diff --git a/Hi3Helper.Sophon b/Hi3Helper.Sophon index 762728b2a..418aa56ab 160000 --- a/Hi3Helper.Sophon +++ b/Hi3Helper.Sophon @@ -1 +1 @@ -Subproject commit 762728b2ac1d396933422e6cb12649e26d6c90c3 +Subproject commit 418aa56abbec9621d33e7b60997fbe5ece823d87 From f20a6cf96fa66d10edf61b2fcaa98a99e3d7fa6d Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 31 Aug 2024 11:29:40 +0700 Subject: [PATCH 24/25] Fix progress bar determinate status set to true --- .../Classes/Interfaces/Class/ProgressBase.cs | 14 ++++++++------ CollapseLauncher/packages.lock.json | 14 ++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 152dd806b..6fc754f17 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -68,7 +68,7 @@ private void Init() protected long _progressAllIOReadCurrent; protected long _progressPerFileSizeCurrent; protected long _progressPerFileSizeTotal; - protected double _progressPerFileIOReadCurrent; + protected long _progressPerFileIOReadCurrent; // Extension for IGameInstallManager @@ -437,7 +437,7 @@ protected async void UpdateSophonFileTotalProgress(long read) // Calculate the speed double speedAll = _progressAllIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; - double speedPerFile = _progressPerFileIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speedPerFile = (_progressPerFileIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); double speedAllNoReset = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; _sophonProgress.ProgressAllSpeed = speedAll; _sophonProgress.ProgressPerFileSpeed = speedPerFile; @@ -446,6 +446,11 @@ protected async void UpdateSophonFileTotalProgress(long read) _sophonProgress.ProgressAllEntryCountCurrent = _progressAllCountCurrent; _sophonProgress.ProgressAllEntryCountTotal = _progressAllCountTotal; + // Always change the status progress to determined + _status.IsProgressAllIndetermined = false; + _status.IsProgressPerFileIndetermined = false; + StatusChanged?.Invoke(this, _status); + // Calculate percentage _sophonProgress.ProgressAllPercentage = Math.Round((double)_progressAllSizeCurrent / _progressAllSizeTotal * 100, 2); @@ -474,10 +479,7 @@ protected async void UpdateSophonFileTotalProgress(long read) protected void UpdateSophonFileDownloadProgress(long downloadedWrite, long currentWrite) { Interlocked.Add(ref _progressPerFileSizeCurrent, downloadedWrite); - lock (_objLock) - { - _progressPerFileIOReadCurrent += currentWrite; - } + Interlocked.Add(ref _progressPerFileIOReadCurrent, currentWrite); } protected void UpdateSophonDownloadStatus(SophonAsset asset) diff --git a/CollapseLauncher/packages.lock.json b/CollapseLauncher/packages.lock.json index 2aed91773..8086e2330 100644 --- a/CollapseLauncher/packages.lock.json +++ b/CollapseLauncher/packages.lock.json @@ -134,9 +134,9 @@ }, "HtmlAgilityPack": { "type": "Direct", - "requested": "[1.11.64, )", - "resolved": "1.11.64", - "contentHash": "5GtlZ7Y/L1k5zBokuOGW96PVTItRv/QCUJWlXiZ5YvYN2DI8wFto+WXBxkF55DdG0WabAG/F3ghqOMGdo89Rlg==" + "requested": "[1.11.65, )", + "resolved": "1.11.65", + "contentHash": "Skse4MVlRcHHyP88x+v/uQrMxTPaBIwQodiQ/y59jxs8vHlqTR5M3jt5Wk+csYFnR4rX6I4ndqDji2M8p9kAzw==" }, "ImageEx": { "type": "Direct", @@ -174,6 +174,12 @@ "resolved": "6.0.0-preview.4.21253.7", "contentHash": "ipvxo/6FRdrmcsAtCGI9QPeaZZSyeCk9LiSVIJ4AN36IZQG7KFtk9ZbbYgvy4wzCzHkbBmTWaf2ESBqrYEtCRg==" }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.1, )", + "resolved": "2.1.1", + "contentHash": "q2CixHYFojIC1As4qUcK+yRIdDsN9UVT96TsI+3wBPab+zDKIv1PqrFOfJgB4Jkpn6iPs7nbLFRH3abVFzBPGA==" + }, "Microsoft.Windows.SDK.BuildTools": { "type": "Direct", "requested": "[10.0.26100.1, )", @@ -994,7 +1000,7 @@ "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.27.3, )", + "Google.Protobuf": "[3.28.0, )", "Hi3Helper.Http": "[2.0.0, )", "System.IO.Hashing": "[8.0.0, )" } From e2109699b3afe11cf9b6e1d381b32ec4eaf29589 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 31 Aug 2024 11:43:30 +0700 Subject: [PATCH 25/25] Use new speed calculation for Cache Update and Game Repair --- .../Classes/Interfaces/Class/ProgressBase.cs | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 6fc754f17..c09953757 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -143,9 +143,11 @@ protected virtual void _httpClient_FetchAssetProgress(object sender, DownloadEve protected virtual async void _httpClient_RepairAssetProgress(int size, DownloadProgress downloadProgress) { Interlocked.Add(ref _progressAllSizeCurrent, size); + Interlocked.Add(ref _progressAllIOReadCurrent, size); + if (await CheckIfNeedRefreshStopwatch()) { - double speed = (_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); + double speed = (_progressAllIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); TimeSpan timeLeftSpan = ((_progressAllSizeCurrent - _progressAllSizeTotal) / speed).ToTimeSpanNormalized(); double percentagePerFile = ConverterTool.GetPercentageNumber(downloadProgress.BytesDownloaded, downloadProgress.BytesTotal, 2); @@ -184,6 +186,12 @@ protected virtual async void _httpClient_RepairAssetProgress(int size, DownloadP // Trigger update UpdateAll(); } + + if (_downloadSpeedRefreshInterval < _downloadSpeedRefreshStopwatch!.ElapsedMilliseconds) + { + _progressAllIOReadCurrent = 0; + _downloadSpeedRefreshStopwatch.Restart(); + } } } @@ -255,11 +263,12 @@ protected virtual void UpdateRepairStatus(string activityStatus, string Activity protected virtual async void _httpClient_UpdateAssetProgress(int size, DownloadProgress downloadProgress) { Interlocked.Add(ref _progressAllSizeCurrent, size); + Interlocked.Add(ref _progressAllIOReadCurrent, size); if (await CheckIfNeedRefreshStopwatch()) { - double speed = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; - TimeSpan timeLeftSpan = ((_progressAllSizeTotal - _progressAllSizeCurrent) / speed.ClampLimitedSpeedNumber()).ToTimeSpanNormalized(); + double speed = (_progressAllIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds).ClampLimitedSpeedNumber(); + TimeSpan timeLeftSpan = ((_progressAllSizeTotal - _progressAllSizeCurrent) / speed).ToTimeSpanNormalized(); double percentage = ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal, 2); // Update current progress percentages and speed @@ -272,6 +281,12 @@ protected virtual async void _httpClient_UpdateAssetProgress(int size, DownloadP + string.Format($"({Lang._Misc.SpeedPerSec})", ConverterTool.SummarizeSizeSimple(speed)) + $" | {timeLeftString}"; + if (_downloadSpeedRefreshInterval < _downloadSpeedRefreshStopwatch!.ElapsedMilliseconds) + { + _progressAllIOReadCurrent = 0; + _downloadSpeedRefreshStopwatch.Restart(); + } + // Trigger update UpdateAll(); } @@ -708,12 +723,14 @@ protected void ResetStatusAndProgressProperty() _progress.ProgressPerFileEntryCountTotal = 0; // Reset all inner counter - _progressAllCountCurrent = 0; - _progressAllCountTotal = 0; - _progressAllSizeCurrent = 0; - _progressAllSizeTotal = 0; - _progressPerFileSizeCurrent = 0; - _progressPerFileSizeTotal = 0; + _progressAllCountCurrent = 0; + _progressAllCountTotal = 0; + _progressAllSizeCurrent = 0; + _progressAllSizeTotal = 0; + _progressAllIOReadCurrent = 0; + _progressPerFileSizeCurrent = 0; + _progressPerFileSizeTotal = 0; + _progressPerFileIOReadCurrent = 0; } }