From 8101e3c064f831d6d39f6c96dbb9b8fba4a4fbcd Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 20 Jul 2024 22:38:44 +0700 Subject: [PATCH 01/39] Initial Game Clean-up implementation --- CollapseLauncher/Classes/ClassesContext.cs | 2 + .../Classes/Extension/UIElementExtensions.cs | 13 +- .../HexStringToBytesConverter.cs | 56 +++ .../SlashToBackslashConverter.cs | 26 ++ .../InstallManagerBase.PkgVersion.cs | 320 ++++++++++++++++++ .../BaseClass/InstallManagerBase.cs | 39 +-- .../InstallManagement/Honkai/HonkaiInstall.cs | 60 ++++ .../Classes/Interfaces/IGameInstallManager.cs | 1 + CollapseLauncher/XAMLs/MainApp/MainPage.xaml | 5 +- .../XAMLs/MainApp/Pages/FileCleanupPage.xaml | 212 ++++++++++++ .../MainApp/Pages/FileCleanupPage.xaml.cs | 193 +++++++++++ .../XAMLs/MainApp/Pages/HomePage.xaml | 23 ++ .../XAMLs/MainApp/Pages/HomePage.xaml.cs | 10 +- .../XAMLs/MainApp/ValueConverters.cs | 9 +- .../Lang/Locale/LangFileCleanupPage.cs | 33 ++ Hi3Helper.Core/Lang/en_US.json | 27 +- Hi3Helper.EncTool | 2 +- 17 files changed, 1002 insertions(+), 29 deletions(-) create mode 100644 CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs create mode 100644 CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs create mode 100644 CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs create mode 100644 CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml create mode 100644 CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs create mode 100644 Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs diff --git a/CollapseLauncher/Classes/ClassesContext.cs b/CollapseLauncher/Classes/ClassesContext.cs index 9f1319822..144c9a8d4 100644 --- a/CollapseLauncher/Classes/ClassesContext.cs +++ b/CollapseLauncher/Classes/ClassesContext.cs @@ -1,6 +1,7 @@ using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; using CollapseLauncher.Helper.LauncherApiLoader.Sophon; using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.InstallManager.Base; using CollapseLauncher.Interfaces; using Hi3Helper.EncTool.Parser.AssetMetadata; using Hi3Helper.Shared.ClassStruct; @@ -28,6 +29,7 @@ namespace CollapseLauncher [JsonSerializable(typeof(GeneralDataProp))] [JsonSerializable(typeof(MasterKeyConfig))] [JsonSerializable(typeof(AudioPCKType[]))] + [JsonSerializable(typeof(LocalFileInfo))] [JsonSerializable(typeof(PresetConfig))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(CacheAsset))] diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index 9137ca6b7..5321228ee 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -165,14 +165,23 @@ internal static void SetElementGridColumnPosition(TElement element, in if (span > 0) Grid.SetColumnSpan(element, span); } - internal static void AddTextBlockNewLine(this TextBlock textBlock, int count = 1) + internal static ref TextBlock AddTextBlockNewLine(this TextBlock textBlock, int count = 1) { while (count-- > 0) { textBlock.Inlines.Add(new LineBreak()); } + return ref Unsafe.AsRef(ref textBlock); } - internal static void AddTextBlockLine(this TextBlock textBlock, string message, FontWeight? weight = null, double size = 14d) + + internal static ref TextBlock AddTextBlockLine(this TextBlock textBlock, string message, bool appendSpaceAtEnd, FontWeight? weight = null, double size = 14d) + { + message += ' '; + return ref textBlock.AddTextBlockLine(message, weight, size); + } + + internal static ref TextBlock AddTextBlockLine(this TextBlock textBlock, string message, FontWeight? weight = null, double size = 14d) { if (!weight.HasValue) weight = FontWeights.Normal; textBlock.Inlines.Add(new Run { Text = message, FontWeight = weight.Value, FontSize = size }); + return ref Unsafe.AsRef(ref textBlock); } internal static TReturnType GetApplicationResource(string resourceKey) diff --git a/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs b/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs new file mode 100644 index 000000000..af5a0301a --- /dev/null +++ b/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs @@ -0,0 +1,56 @@ +using Hi3Helper.Data; +using System; +using System.Buffers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable +namespace CollapseLauncher.Helper.JsonConverter +{ + internal class HexStringToBytesConverter : JsonConverter + { + public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Current type token is not a string!"); + + ReadOnlySpan charSpan = reader.ValueSpan; + if (charSpan.Length % 2 != 0) + throw new JsonException($"String does not have an even length! (Length: {charSpan.Length} characters)"); + + int bufferAllocLen = charSpan.Length >> 1; + bool useRent = charSpan.Length <= 64 << 10; + byte[] returnBuffer = GC.AllocateUninitializedArray(bufferAllocLen); + char[] stringBuffer = useRent ? ArrayPool.Shared.Rent(charSpan.Length) : new char[charSpan.Length]; + + try + { + if (!(Encoding.UTF8.TryGetChars(reader.ValueSpan, stringBuffer, out int charsWritten) && charsWritten % 2 == 0)) + throw new JsonException("Failed while converting JSON UTF-8 string to .NET/Unicode string"); + + if (!HexTool.TryHexToBytesUnsafe(stringBuffer, returnBuffer)) + throw new JsonException("Failed while converting string to hex"); + + return returnBuffer; + } + finally + { + if (useRent) ArrayPool.Shared.Return(stringBuffer); + } + } + + public override unsafe void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) + { + if (value.Length % 2 != 0) + throw new JsonException($"Bytes Array does not have an even length! (Length: {value.Length} characters)"); + + int bufferAllocLen = value.Length << 1; + char[] stringBuffer = GC.AllocateUninitializedArray(bufferAllocLen); + if (!HexTool.TryBytesToHexUnsafe(value, stringBuffer)) + throw new JsonException("Failed while converting bytes to hex"); + + writer.WriteStringValue(stringBuffer); + } + } +} diff --git a/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs b/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs new file mode 100644 index 000000000..f5252fecf --- /dev/null +++ b/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs @@ -0,0 +1,26 @@ +using Hi3Helper.Data; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable +namespace CollapseLauncher.Helper.JsonConverter +{ + internal class SlashToBackslashConverter : JsonConverter + { + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Current type token is not a string!"); + + string? str = reader.GetString(); + return ConverterTool.NormalizePath(str); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + string replaced = value.Replace('\\', '/'); + writer.WriteStringValue(replaced); + } + } +} diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs new file mode 100644 index 000000000..a4bb7d8c5 --- /dev/null +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -0,0 +1,320 @@ +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.JsonConverter; +using CollapseLauncher.Helper.Loading; +using CollapseLauncher.Pages; +using Hi3Helper.Data; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media.Animation; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Hashing; +using System.Linq; +using System.Security.Cryptography; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable +namespace CollapseLauncher.InstallManager.Base +{ + public class LocalFileInfo + { + [JsonPropertyName("remoteName")] + [JsonConverter(typeof(SlashToBackslashConverter))] + public string RelativePath { get; set; } + + [JsonPropertyName("md5")] + [JsonConverter(typeof(HexStringToBytesConverter))] + public byte[] MD5Hash { get; set; } + + [JsonPropertyName("hash")] + [JsonConverter(typeof(HexStringToBytesConverter))] + public byte[] XXH64Hash { get; set; } + + [JsonPropertyName("fileSize")] + public long FileSize { get; set; } + + [JsonIgnore] + public string FullPath { get; set; } + + [JsonIgnore] + public string FileName { get; set; } + + [JsonIgnore] + public bool IsFileExist { get; set; } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + public LocalFileInfo() { } + + public LocalFileInfo(FileInfo fileInfo, string basePath) + { + FullPath = fileInfo.FullName; + FileName = Path.GetFileName(fileInfo.FullName); + RelativePath = GetRelativePath(FullPath, basePath); + Update(); + } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + private string GetRelativePath(string fullPath, string basePath) + { + ReadOnlySpan relativePath = fullPath.AsSpan(basePath.Length).TrimStart('\\'); + return relativePath.ToString(); + } + + public void Update() + { + FileInfo fileInfo = new FileInfo(FullPath); + IsFileExist = fileInfo.Exists; + if (fileInfo.Exists) + { + FileSize = fileInfo.Length; + } + } + + public FileInfo ToFileInfo() => new FileInfo(string.IsNullOrEmpty(FullPath) ? RelativePath : FullPath); + + public async ValueTask UpdateFileHash(CancellationToken token) + { + if (!IsFileExist) return; + + using FileStream stream = File.Open(FullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + + MD5Hash = await MD5.HashDataAsync(stream, token); + stream.Position = 0; + XxHash64 xxh64 = new XxHash64(); + await xxh64.AppendAsync(stream, token); + XXH64Hash = xxh64.GetHashAndReset(); + } + + public override string ToString() + { + return RelativePath ?? string.Empty; + } + } + + internal partial class InstallManagerBase + { + public virtual async ValueTask CleanUpGameFiles(bool withDialog = true) + { + // Get the unused file info asynchronously + List unusedFileInfo = await GetUnusedFileInfoList(withDialog); + + // Spawn dialog if used + if (withDialog) + { + if (WindowUtility.CurrentWindow is MainWindow mainWindow) + { + mainWindow?.overlayFrame.BackStack?.Clear(); + mainWindow?.overlayFrame.Navigate(typeof(NullPage)); + mainWindow?.overlayFrame.Navigate(typeof(FileCleanupPage), null, new DrillInNavigationTransitionInfo()); + } + if (FileCleanupPage.Current != null) + { + FileCleanupPage.Current.InjectFileInfoSource(unusedFileInfo); + FileCleanupPage.Current.MenuExitButton.Click += ExitFromOverlay; + FileCleanupPage.Current.MenuReScanButton.Click += ExitFromOverlay; + FileCleanupPage.Current.MenuReScanButton.Click += async (_, _) => + { + await Task.Delay(250); + await CleanUpGameFiles(true); + }; + } + + return; + } + + // Delete the file straight forward if dialog is not used + foreach (LocalFileInfo fileInfo in unusedFileInfo) + base.TryDeleteReadOnlyFile(fileInfo.FullPath); + + static void ExitFromOverlay(object? sender, RoutedEventArgs args) + { + if (WindowUtility.CurrentWindow is MainWindow mainWindow) + { + mainWindow?.overlayFrame.GoBack(); + mainWindow?.overlayFrame.BackStack?.Clear(); + } + } + } + + protected virtual async Task> GetUnusedFileInfoList(bool includeZipCheck) + { + LoadingMessageHelper.ShowLoadingFrame(); + try + { + // Initialize uninstall game property + _uninstallGameProperty ??= AssignUninstallFolders(); + if (!_uninstallGameProperty.HasValue) + throw new NotSupportedException("Clean-up feature for this game is not yet supported!"); + + // Try parse the pkg_versions (including the audio one) + List pkgFileInfo = new List(); + HashSet pkgFileInfoHashSet = new HashSet(); + await ParsePkgVersions2FileInfo(pkgFileInfo, pkgFileInfoHashSet, _token.Token); + + // Get the list of the local file paths + List localFileInfo = new List(); + await GetRelativeLocalFilePaths(localFileInfo, includeZipCheck, _token.Token); + + // Get and filter the unused file from the pkg_versions + List unusedFileInfo = new List(); + await Task.Run(() => + Parallel.ForEach(localFileInfo, + new ParallelOptions { CancellationToken = _token.Token }, + (asset, _) => + { + if (!pkgFileInfoHashSet.Contains(asset.RelativePath)) + { + lock (unusedFileInfo) + { + unusedFileInfo.Add(asset); + } + } + })); + + return unusedFileInfo; + } + finally + { + LoadingMessageHelper.HideLoadingFrame(); + } + } + + protected virtual async ValueTask ParsePkgVersions2FileInfo(List pkgFileInfo, HashSet pkgFileInfoHashSet, CancellationToken token) + { + string gamePath = _gamePath; + + // Iterate the pkg_version file paths + foreach (string pkgPath in Directory.EnumerateFiles(gamePath, "*pkg_version", SearchOption.TopDirectoryOnly)) + { + // Parse and add the entries to the list + await InnerParsePkgVersion2FileInfo(gamePath, pkgPath, pkgFileInfo, pkgFileInfoHashSet, token); + } + } + + protected virtual async ValueTask InnerParsePkgVersion2FileInfo(string gamePath, string path, List pkgFileInfo, HashSet pkgFileInfoHashSet, CancellationToken token) + { + // Assign path to reader + using StreamReader reader = new StreamReader(path, true); + // Do loop until EOF + while (!reader.EndOfStream) + { + // Read line and deserialize + string? line = await reader.ReadLineAsync(token); + LocalFileInfo? localFileInfo = line?.Deserialize(InternalAppJSONContext.Default); + + // Assign the values + if (localFileInfo != null) + { + localFileInfo.FullPath = Path.Combine(gamePath, localFileInfo.RelativePath); + localFileInfo.FileName = Path.GetFileName(localFileInfo.RelativePath); + localFileInfo.IsFileExist = File.Exists(localFileInfo.FullPath); + + // Add it to the list and hashset + pkgFileInfo.Add(localFileInfo); + pkgFileInfoHashSet.Add(localFileInfo.RelativePath); + } + } + } + + protected virtual bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, out LocalFileInfo localFileInfo) + { + // Convert to LocalFileInfo and get the relative path + localFileInfo = new LocalFileInfo(fileInfo, gamePath); + string relativePath = localFileInfo.RelativePath; + ReadOnlySpan relativePathSpan = relativePath; + string fileName = localFileInfo.FileName; + string gameFolder = _uninstallGameProperty?.gameDataFolderName ?? string.Empty; + string persistentPath = Path.Combine(gameFolder, "Persistent"); + + // 1st check: Ensure that the file is not a persistent file + if (relativePathSpan.StartsWith(persistentPath, StringComparison.OrdinalIgnoreCase)) + return false; + + // 2nd check: Ensure that the file is not a config or pkg_version file + if (relativePathSpan.EndsWith("config.ini", StringComparison.OrdinalIgnoreCase) + || relativePathSpan.EndsWith("pkg_version", StringComparison.OrdinalIgnoreCase)) + return false; + + // 3rd check: Ensure that the file is not a web cache file + if (relativePathSpan.Contains("webCache", StringComparison.OrdinalIgnoreCase) + || relativePathSpan.Contains("SDKCache", StringComparison.OrdinalIgnoreCase)) + return false; + + // 4th check: Ensure that the file isn't in excluded list + if (_uninstallGameProperty?.foldersToKeepInData + .Any(x => relativePath + .AsSpan() // As Span since StartsWith() in it is typically faster + // than the one from String primitive + .Contains(x.AsSpan(), StringComparison.OrdinalIgnoreCase)) ?? false) + return false; // Return false if it's not actually in excluded list + + // 5th check: Ensure if the path includes the folder name at start + if (!string.IsNullOrEmpty(gameFolder) && relativePathSpan + .StartsWith(gameFolder, StringComparison.OrdinalIgnoreCase)) + return true; + + // 6th check: Ensure if the path includes the DXSETUP folder + if (Path.GetDirectoryName(relativePathSpan) + .EndsWith("DXSETUP", StringComparison.OrdinalIgnoreCase)) + return true; + + // 7th check: Ensure that the file is one of the files included + // in the Regex pattern list + if (_uninstallGameProperty?.filesToDelete + .Any(pattern => Regex.IsMatch(fileName, + pattern, + RegexOptions.Compiled | + RegexOptions.NonBacktracking + )) ?? false) + return true; + + // 8th check: Ensure that the file is one of package files + if (includeZipCheck && Regex.IsMatch(fileName, + @"(\.[0-9][0-9][0-9]|zip|7z|patch)$", + RegexOptions.Compiled | + RegexOptions.NonBacktracking + )) return true; + + // If all those matches failed, then return them as a non-game file + return false; + } + + protected virtual async Task GetRelativeLocalFilePaths(List localFileInfoList, bool includeZipCheck, CancellationToken token) + { + await Task.Run(() => + { + int count = 0; + long totalSize = 0; + string gamePath = _gamePath; + DirectoryInfo dirInfo = new DirectoryInfo(gamePath); + + // Do the do in parallel since it will be a really CPU expensiven task due to janky checks here and there. + Parallel.ForEach(dirInfo + .EnumerateFiles("*", SearchOption.AllDirectories), + new ParallelOptions { CancellationToken = token }, + (fileInfo, _) => + { + // Throw if token is cancelled + token.ThrowIfCancellationRequested(); + + // Do the check within the lambda function to possibly check the file + // condition in multithread + if (IsCategorizedAsGameFile(fileInfo, gamePath, includeZipCheck, out LocalFileInfo localFileInfo)) + { + Interlocked.Add(ref totalSize, fileInfo.Exists ? fileInfo.Length : 0); + Interlocked.Increment(ref count); + _parentUI.DispatcherQueue.TryEnqueue(() => + LoadingMessageHelper.SetMessage("Processing", $"Calculating Existing Files ({count} file(s) found - {ConverterTool.SummarizeSizeSimple(totalSize)} in total)...")); + lock (localFileInfoList) + { + localFileInfoList.Add(localFileInfo); + } + } + }); + }, token); + } + } +} diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 26f4b15c9..382f014c3 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -136,6 +136,7 @@ protected bool _isSophonPreloadCompleted } } protected List _sophonVOLanguageList { get; set; } = new(); + protected UninstallGameProperty? _uninstallGameProperty { get; set; } #endregion @@ -1553,20 +1554,20 @@ public async ValueTask UninstallGame() { #nullable enable // Assign UninstallProperty from each overrides - UninstallGameProperty UninstallProperty = AssignUninstallFolders(); + _uninstallGameProperty ??= AssignUninstallFolders(); //Preparing paths - string _DataFolderFullPath = Path.Combine(GameFolder, UninstallProperty.gameDataFolderName); + string _DataFolderFullPath = Path.Combine(GameFolder, _uninstallGameProperty?.gameDataFolderName ?? ""); string[]? foldersToKeepInDataFullPath; - if (UninstallProperty.foldersToKeepInData != null && UninstallProperty.foldersToKeepInData.Length != 0) + if (_uninstallGameProperty?.foldersToKeepInData != null && _uninstallGameProperty?.foldersToKeepInData.Length != 0) { - foldersToKeepInDataFullPath = new string[UninstallProperty.foldersToKeepInData.Length]; - for (int i = 0; i < UninstallProperty.foldersToKeepInData.Length; i++) + foldersToKeepInDataFullPath = new string[_uninstallGameProperty?.foldersToKeepInData?.Length ?? 0]; + for (int i = 0; i < (_uninstallGameProperty?.foldersToKeepInData?.Length ?? 0); i++) { // ReSharper disable once AssignNullToNotNullAttribute foldersToKeepInDataFullPath[i] = - Path.Combine(_DataFolderFullPath, UninstallProperty.foldersToKeepInData[i]); + Path.Combine(_DataFolderFullPath, _uninstallGameProperty?.foldersToKeepInData[i] ?? ""); } } else foldersToKeepInDataFullPath = Array.Empty(); @@ -1574,10 +1575,10 @@ public async ValueTask UninstallGame() #pragma warning disable CS8604 // Possible null reference argument. LogWriteLine($"Uninstalling game: {_gameVersionManager.GameType} - region: {_gameVersionManager.GamePreset.ZoneName ?? string.Empty}\r\n" + $" GameFolder : {GameFolder}\r\n" + - $" gameDataFolderName : {UninstallProperty.gameDataFolderName}\r\n" + - $" foldersToDelete : {string.Join(", ", UninstallProperty.foldersToDelete)}\r\n" + - $" filesToDelete : {string.Join(", ", UninstallProperty.filesToDelete)}\r\n" + - $" foldersToKeepInData : {string.Join(", ", UninstallProperty.foldersToKeepInData)}\r\n" + + $" gameDataFolderName : {_uninstallGameProperty?.gameDataFolderName}\r\n" + + $" foldersToDelete : {string.Join(", ", _uninstallGameProperty?.foldersToDelete)}\r\n" + + $" filesToDelete : {string.Join(", ", _uninstallGameProperty?.filesToDelete)}\r\n" + + $" foldersToKeepInData : {string.Join(", ", _uninstallGameProperty?.foldersToKeepInData)}\r\n" + $" _Data folder path : {_DataFolderFullPath}\r\n" + $" Excluded full paths : {string.Join(", ", foldersToKeepInDataFullPath)}", LogType.Warning, true); @@ -1589,8 +1590,8 @@ public async ValueTask UninstallGame() try { // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (UninstallProperty.foldersToKeepInData != null && - UninstallProperty.foldersToKeepInData.Length != 0 && + if (_uninstallGameProperty?.foldersToKeepInData != null && + _uninstallGameProperty?.foldersToKeepInData.Length != 0 && !foldersToKeepInDataFullPath .Contains(folderGameData)) // Skip this entire process if foldersToKeepInData is null { @@ -1629,8 +1630,8 @@ public async ValueTask UninstallGame() // Cleanup any folders in foldersToDelete foreach (string folderNames in Directory.EnumerateDirectories(GameFolder)) { - if (UninstallProperty.foldersToDelete.Length != 0 && - UninstallProperty.foldersToDelete.Contains(Path.GetFileName(folderNames))) + if (_uninstallGameProperty?.foldersToDelete.Length != 0 && + (_uninstallGameProperty?.foldersToDelete.Contains(Path.GetFileName(folderNames)) ?? false)) { try { @@ -1648,14 +1649,14 @@ public async ValueTask UninstallGame() // Cleanup any files in filesToDelete foreach (string fileNames in Directory.EnumerateFiles(GameFolder)) { - if (UninstallProperty.filesToDelete.Length != 0 && - UninstallProperty.filesToDelete.Contains(Path.GetFileName(fileNames)) || - UninstallProperty.filesToDelete.Length != 0 && - UninstallProperty.filesToDelete.Any(pattern => Regex.IsMatch(Path.GetFileName(fileNames), + if (_uninstallGameProperty?.filesToDelete.Length != 0 && + (_uninstallGameProperty?.filesToDelete.Contains(Path.GetFileName(fileNames)) ?? false) || + _uninstallGameProperty?.filesToDelete.Length != 0 && + (_uninstallGameProperty?.filesToDelete.Any(pattern => Regex.IsMatch(Path.GetFileName(fileNames), pattern, RegexOptions.Compiled | RegexOptions.NonBacktracking - ))) + )) ?? false)) { TryDeleteReadOnlyFile(fileNames); LogWriteLine($"Deleted {fileNames}", LogType.Default, true); diff --git a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs index 0272ff37c..bb84f10f7 100644 --- a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using static CollapseLauncher.Dialogs.SimpleDialogs; using static Hi3Helper.Logger; @@ -129,5 +130,64 @@ private string GetFailedGameConversionFolder(string basepath) foldersToKeepInData = Array.Empty() }; #endregion + + #region Override Methods - CleanUpGameFiles + protected override bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, out LocalFileInfo localFileInfo) + { + // Convert to LocalFileInfo and get the relative path + localFileInfo = new LocalFileInfo(fileInfo, gamePath); + string relativePath = localFileInfo.RelativePath; + ReadOnlySpan relativePathSpan = relativePath; + string fileName = localFileInfo.FileName; + string gameFolder = _uninstallGameProperty?.gameDataFolderName ?? string.Empty; + + // 1st check: Ensure that the file is not a config or pkg_version file + if (relativePathSpan.EndsWith("config.ini", StringComparison.OrdinalIgnoreCase) + || relativePathSpan.EndsWith("pkg_version", StringComparison.OrdinalIgnoreCase)) + return false; + + // 2nd check: Ensure that the file is not a web cache file + if (relativePathSpan.Contains("webCache", StringComparison.OrdinalIgnoreCase) + || relativePathSpan.Contains("SDKCache", StringComparison.OrdinalIgnoreCase)) + return false; + + // 3rd check: Ensure that the file isn't in excluded list + if (_uninstallGameProperty?.foldersToKeepInData + .Any(x => relativePath + .AsSpan() // As Span since StartsWith() in it is typically faster + // than the one from String primitive + .Contains(x.AsSpan(), StringComparison.OrdinalIgnoreCase)) ?? false) + return false; // Return false if it's not actually in excluded list + + // 4th check: Ensure that the file is not a StreamingAssets file + if (relativePathSpan.Contains("StreamingAssets", StringComparison.OrdinalIgnoreCase)) + return false; + + // 5th check: Ensure if the path includes the folder name at start + if (!string.IsNullOrEmpty(gameFolder) && relativePathSpan + .StartsWith(gameFolder, StringComparison.OrdinalIgnoreCase)) + return true; + + // 6th check: Ensure that the file is one of the files included + // in the Regex pattern list + if (_uninstallGameProperty?.filesToDelete + .Any(pattern => Regex.IsMatch(fileName, + pattern, + RegexOptions.Compiled | + RegexOptions.NonBacktracking + )) ?? false) + return true; + + // 7th check: Ensure that the file is one of package files + if (includeZipCheck && Regex.IsMatch(fileName, + @"(\.[0-9][0-9][0-9]|zip|7z|patch)$", + RegexOptions.Compiled | + RegexOptions.NonBacktracking + )) return true; + + // If all those matches failed, then return them as a non-game file + return false; + } + #endregion } } diff --git a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs index 96ac1e580..61385396c 100644 --- a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs +++ b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs @@ -22,6 +22,7 @@ internal interface IGameInstallManager : IBackgroundActivity, IDisposable ValueTask TryShowFailedDeltaPatchState(); ValueTask TryShowFailedGameConversionState(); + ValueTask CleanUpGameFiles(bool withDialog = true); void UpdateCompletenessStatus(CompletenessStatus status); diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 9aca945f7..a3c33eaf0 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -1,4 +1,4 @@ - + - + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml new file mode 100644 index 000000000..c1979c2b0 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs new file mode 100644 index 000000000..627b068bd --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs @@ -0,0 +1,193 @@ +using CollapseLauncher.CustomControls; +using CollapseLauncher.Dialogs; +using CollapseLauncher.Extension; +using CollapseLauncher.InstallManager.Base; +using Hi3Helper; +using Hi3Helper.Data; +using Microsoft.UI.Text; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +#nullable enable +namespace CollapseLauncher.Pages +{ + public sealed partial class FileCleanupPage : Page + { + internal static FileCleanupPage? Current { get; set; } + internal ObservableCollection FileInfoSource = new ObservableCollection(); + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + public FileCleanupPage() + { + this.InitializeComponent(); + Current = this; + Loaded += (_, _) => + { + foreach (LocalFileInfo asset in FileInfoSource) + ListViewTable.SelectedItems.Add(asset); + + FileInfoSource.CollectionChanged += UpdateUIOnCollectionChange; + }; + } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + private bool? IsAllSelected; + private int SelectedAssetsCount; + private long AssetTotalSize; + private string AssetTotalSizeString = string.Empty; + private long AssetSelectedSize; + + public void InjectFileInfoSource(IEnumerable fileInfoList) + { + FileInfoSource.Clear(); + foreach (LocalFileInfo fileInfo in fileInfoList) + { + FileInfoSource.Add(fileInfo); + AssetTotalSize += fileInfo.FileSize; + } + AssetTotalSizeString = ConverterTool.SummarizeSizeSimple(AssetTotalSize); + UpdateUIOnCollectionChange(FileInfoSource, null); + } + + private void UpdateUIOnCollectionChange(object? sender, NotifyCollectionChangedEventArgs? args) + { + ObservableCollection? obj = (ObservableCollection?)sender; + int count = obj?.Count ?? 0; + bool isHasValue = count > 0; + ListViewTable.Opacity = isHasValue ? 1 : 0; + NoFilesTextGrid.Opacity = isHasValue ? 0 : 1; + + ToggleCheckAllCheckBox.IsEnabled = isHasValue; + DeleteAllFiles.IsEnabled = isHasValue; + DeleteSelectedFiles.IsEnabled = isHasValue && SelectedAssetsCount > 0; + } + + + private void ToggleCheckAll(object sender, RoutedEventArgs e) + { + if (sender is CheckBox checkBox) + { + bool toCheck = checkBox.IsChecked ?? false; + SelectAllToggle(toCheck); + } + } + + private void SelectAllToggle(bool selectAll) + { + if (selectAll) + foreach (LocalFileInfo asset in FileInfoSource) + { + if (ListViewTable.SelectedItems.IndexOf(asset) < 0) + ListViewTable.SelectedItems.Add(asset); + } + else + ListViewTable.SelectedItems.Clear(); + } + + private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedAssetsCount -= e.RemovedItems.Count; + AssetSelectedSize -= e.RemovedItems.OfType().Sum(x => x.FileSize); + + SelectedAssetsCount += e.AddedItems.Count; + AssetSelectedSize += e.AddedItems.OfType().Sum(x => x.FileSize); + + ToggleCheckAllCheckBox.Content = SelectedAssetsCount > 0 ? + string.Format(Locale.Lang._FileCleanupPage.BottomCheckboxFilesSelected, + SelectedAssetsCount, ConverterTool.SummarizeSizeSimple(AssetSelectedSize), AssetTotalSizeString) : + Locale.Lang._FileCleanupPage.BottomCheckboxNoFileSelected; + DeleteSelectedFilesText.Text = string.Format(Locale.Lang._FileCleanupPage.BottomButtonDeleteSelectedFiles, SelectedAssetsCount); + DeleteSelectedFiles.IsEnabled = SelectedAssetsCount > 0; + + if (SelectedAssetsCount != 0 && SelectedAssetsCount != FileInfoSource.Count) + ToggleCheckAllCheckBox.IsChecked = null; + else + ToggleCheckAllCheckBox.IsChecked = SelectedAssetsCount == FileInfoSource.Count; + } + + private async void DeleteAllFiles_Click(object sender, RoutedEventArgs e) + { + IList fileInfoList = FileInfoSource + .ToList(); + long size = fileInfoList.Sum(x => x.FileSize); + await PerformRemoval(fileInfoList, size); + } + + private async void DeleteSelectedFiles_Click(object sender, RoutedEventArgs e) + { + IList fileInfoList = ListViewTable.SelectedItems + .OfType() + .ToList(); + long size = fileInfoList.Sum(x => x.FileSize); + await PerformRemoval(fileInfoList, size); + } + + private async Task PerformRemoval(IList deletionSource, long totalSize) + { + if (deletionSource == null) return; + + TextBlock textBlockMsg = new TextBlock() { + TextAlignment = TextAlignment.Center, + TextWrapping = TextWrapping.WrapWholeWords + }.AddTextBlockLine(Locale.Lang._FileCleanupPage.DialogDeletingFileSubtitle1, true) + .AddTextBlockLine(string.Format(Locale.Lang._FileCleanupPage.DialogDeletingFileSubtitle2, deletionSource.Count), true, FontWeights.Medium) + .AddTextBlockLine(Locale.Lang._FileCleanupPage.DialogDeletingFileSubtitle3, true) + .AddTextBlockLine(string.Format(Locale.Lang._FileCleanupPage.DialogDeletingFileSubtitle4, ConverterTool.SummarizeSizeSimple(totalSize)), FontWeights.Medium) + .AddTextBlockNewLine() + .AddTextBlockLine(Locale.Lang._FileCleanupPage.DialogDeletingFileSubtitle5); + + ContentDialogResult dialogResult = await SimpleDialogs.SpawnDialog( + Locale.Lang._FileCleanupPage.DialogDeletingFileTitle, + textBlockMsg, + this, + Locale.Lang._Misc.NoCancel, + Locale.Lang._Misc.YesContinue, + null, + ContentDialogButton.Close, + ContentDialogTheme.Warning); + if (ContentDialogResult.Primary != dialogResult) return; + + int deleteSuccess = 0; + int deleteFailed = 0; + foreach (LocalFileInfo fileInfo in deletionSource) + { + try + { + FileInfo fileInfoN = fileInfo.ToFileInfo(); + if (fileInfoN.Exists) + { + fileInfoN.IsReadOnly = false; + fileInfoN.Delete(); + } + + FileInfoSource.Remove(fileInfo); + + ++deleteSuccess; + } + catch (Exception ex) + { + ++deleteFailed; + Logger.LogWriteLine($"Failed while deleting this file: {fileInfo.FullPath}\r\n{ex}", LogType.Error, true); + } + } + + await SimpleDialogs.SpawnDialog(Locale.Lang._FileCleanupPage.DialogDeleteSuccessTitle, + string.Format(Locale.Lang._FileCleanupPage.DialogDeleteSuccessSubtitle1, deleteSuccess) + + (deleteFailed == 0 ? string.Empty : + ' ' + string.Format(Locale.Lang._FileCleanupPage.DialogDeleteSuccessSubtitle2, deleteFailed)), + this, + Locale.Lang._Misc.OkayHappy, + null, + null, + ContentDialogButton.Close, + ContentDialogTheme.Success); + } + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 4bf7b7b53..d12fe471f 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -1406,6 +1406,29 @@ + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index e44b7355d..78c8f72ca 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -10,6 +10,7 @@ using CollapseLauncher.Helper.Animation; using CollapseLauncher.Helper.Image; using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.InstallManager.Base; using CollapseLauncher.Interfaces; using CollapseLauncher.ShortcutUtils; using CollapseLauncher.Statics; @@ -55,7 +56,6 @@ using Size = System.Drawing.Size; using Timer = System.Timers.Timer; using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions; -using CollapseLauncher.InstallManager.Base; namespace CollapseLauncher.Pages { @@ -834,6 +834,7 @@ private async ValueTask GetCurrentGameState() UninstallGameButton.IsEnabled = false; RepairGameButton.IsEnabled = false; OpenGameFolderButton.IsEnabled = false; + CleanupFilesButton.IsEnabled = false; OpenCacheFolderButton.IsEnabled = false; ConvertVersionButton.IsEnabled = false; CustomArgsTextBox.IsEnabled = false; @@ -1919,6 +1920,13 @@ private void OpenScreenshotFolderButton_Click(object sender, RoutedEventArgs e) } }.Start(); } + + private async void CleanupFilesButton_Click(object sender, RoutedEventArgs e) + { + GameStartupSetting.Flyout.Hide(); + if (CurrentGameProperty?._GameInstall != null) + await CurrentGameProperty._GameInstall.CleanUpGameFiles(true); + } #endregion #region Game Management Buttons diff --git a/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs b/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs index 7555ae02f..2bbc8e86d 100644 --- a/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs +++ b/CollapseLauncher/XAMLs/MainApp/ValueConverters.cs @@ -1,4 +1,5 @@ -using Microsoft.UI.Xaml; +using Hi3Helper.Data; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data; using System; @@ -43,4 +44,10 @@ public class DoubleRound3Converter : IValueConverter public object Convert(object value, Type targetType, object parameter, string input) => Math.Round((double)value, 3); public object ConvertBack(object value, Type targetType, object parameter, string input) => new NotImplementedException(); } + + 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(); + } } diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs new file mode 100644 index 000000000..7963a7bf0 --- /dev/null +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -0,0 +1,33 @@ +namespace Hi3Helper +{ + public sealed partial class Locale + { + #region FileCleanupPage + public sealed partial class LocalizationParams + { + public LangFileCleanupPage _FileCleanupPage { get; set; } = LangFallback?._FileCleanupPage; + public sealed class LangFileCleanupPage + { + public string Title { get; set; } = LangFallback?._FileCleanupPage.Title; + public string TopButtonRescan { get; set; } = LangFallback?._FileCleanupPage.TopButtonRescan; + public string NoFilesToBeDeletedText { get; set; } = LangFallback?._FileCleanupPage.NoFilesToBeDeletedText; + public string ListViewFieldFileName { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileName; + public string ListViewFieldFileSize { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileSize; + public string BottomButtonDeleteAllFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteAllFiles; + public string BottomButtonDeleteSelectedFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteSelectedFiles; + public string BottomCheckboxFilesSelected { get; set; } = LangFallback?._FileCleanupPage.BottomCheckboxFilesSelected; + public string BottomCheckboxNoFileSelected { get; set; } = LangFallback?._FileCleanupPage.BottomCheckboxNoFileSelected; + public string DialogDeletingFileTitle { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileTitle; + public string DialogDeletingFileSubtitle1 { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileSubtitle1; + public string DialogDeletingFileSubtitle2 { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileSubtitle2; + public string DialogDeletingFileSubtitle3 { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileSubtitle3; + public string DialogDeletingFileSubtitle4 { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileSubtitle4; + public string DialogDeletingFileSubtitle5 { get; set; } = LangFallback?._FileCleanupPage.DialogDeletingFileSubtitle5; + public string DialogDeleteSuccessTitle { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessTitle; + public string DialogDeleteSuccessSubtitle1 { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessSubtitle1; + public string DialogDeleteSuccessSubtitle2 { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessSubtitle2; + } + } + #endregion + } +} diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 9413797cf..08d441934 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -175,7 +175,7 @@ "CommunityToolsBtn_OpenExecutableAppDialogTitle": "Select Program Executable: {0}", "CreateShortcut_FolderPicker": "Select where to place the shortcut", - + "Exception_DownloadTimeout1": "Timeout occurred when trying to install {0}", "Exception_DownloadTimeout2": "Check stability of your internet! If your internet speed is slow, please lower the download thread count.", "Exception_DownloadTimeout3": "**WARNING** Changing download thread count WILL reset your download from 0, and you have to delete the existing download chunks manually!" @@ -473,7 +473,7 @@ "About_Copyright2": " neon-nyan, Cry0, bagusnl,\r\n shatyuka & gablm", "About_Copyright3": "Under", "About_Copyright4": ". All rights reserved.", - + "LicenseType": "MIT License", "Disclaimer": "Disclaimer", @@ -1262,6 +1262,27 @@ "AgreementTitle": "Agreement:" }, + "_FileCleanupPage": { + "Title": "Files Clean-up", + "TopButtonRescan": "Re-Scan", + "NoFilesToBeDeletedText": "No files need to be deleted!", + "ListViewFieldFileName": "File Name", + "ListViewFieldFileSize": "File Size", + "BottomButtonDeleteAllFiles": "Delete All Files", + "BottomButtonDeleteSelectedFiles": "Delete {0} Selected File(s)", + "BottomCheckboxFilesSelected": "{0} File(s) Selected ({1} / {2} Total)", + "BottomCheckboxNoFileSelected": "No File Selected", + "DialogDeletingFileTitle": "Deleting Files", + "DialogDeletingFileSubtitle1": "You're about to", + "DialogDeletingFileSubtitle2": "delete {0} file(s)", + "DialogDeletingFileSubtitle3": "which contains", + "DialogDeletingFileSubtitle4": "{0} in size.", + "DialogDeletingFileSubtitle5": "Are you sure to delete the files?", + "DialogDeleteSuccessTitle": "Files Deleted!", + "DialogDeleteSuccessSubtitle1": "{0} file(s) have been successfully deleted", + "DialogDeleteSuccessSubtitle2": "with {0} failed to delete" + }, + "_OOBEStartUpMenu": { "WelcomeTitleString": { "Upper": [ "Welcome", " To" ], @@ -1299,7 +1320,7 @@ "CDNCheckboxItemLatencyUnknownFormat": " (unknown)", "CDNCheckboxItemLatencyRecommendedFormat": " [Recommended]" }, - + "_ZenlessGameSettingsPage": { "Graphics_ColorFilter": "Color Filter Strength", "Graphics_RenderRes": "Render Resolution", diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index 3cb677038..b11991bfa 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit 3cb67703833a4007dbf54bbe1b3230910f6a92e1 +Subproject commit b11991bfa6f5c1e6037e30b79aac88084b02d048 From c25f807f34dfd42ce9745c229feb3bb6547f16ad Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 20 Jul 2024 22:44:26 +0700 Subject: [PATCH 02/39] FIx missing loading localization --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 8 +++++++- Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs | 2 ++ Hi3Helper.Core/Lang/en_US.json | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index a4bb7d8c5..1a0cf7393 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -2,6 +2,7 @@ using CollapseLauncher.Helper.JsonConverter; using CollapseLauncher.Helper.Loading; using CollapseLauncher.Pages; +using Hi3Helper; using Hi3Helper.Data; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Animation; @@ -307,7 +308,12 @@ await Task.Run(() => Interlocked.Add(ref totalSize, fileInfo.Exists ? fileInfo.Length : 0); Interlocked.Increment(ref count); _parentUI.DispatcherQueue.TryEnqueue(() => - LoadingMessageHelper.SetMessage("Processing", $"Calculating Existing Files ({count} file(s) found - {ConverterTool.SummarizeSizeSimple(totalSize)} in total)...")); + LoadingMessageHelper.SetMessage( + Locale.Lang._FileCleanupPage.LoadingTitle, + string.Format(Locale.Lang._FileCleanupPage.LoadingSubtitle, + count, + ConverterTool.SummarizeSizeSimple(totalSize)) + )); lock (localFileInfoList) { localFileInfoList.Add(localFileInfo); diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs index 7963a7bf0..5cef001dd 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -13,6 +13,8 @@ public sealed class LangFileCleanupPage public string NoFilesToBeDeletedText { get; set; } = LangFallback?._FileCleanupPage.NoFilesToBeDeletedText; public string ListViewFieldFileName { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileName; public string ListViewFieldFileSize { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileSize; + public string LoadingTitle { get; set; } = LangFallback?._FileCleanupPage.LoadingTitle; + public string LoadingSubtitle { get; set; } = LangFallback?._FileCleanupPage.LoadingSubtitle; public string BottomButtonDeleteAllFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteAllFiles; public string BottomButtonDeleteSelectedFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteSelectedFiles; public string BottomCheckboxFilesSelected { get; set; } = LangFallback?._FileCleanupPage.BottomCheckboxFilesSelected; diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 08d441934..0e0f36e15 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -1268,6 +1268,8 @@ "NoFilesToBeDeletedText": "No files need to be deleted!", "ListViewFieldFileName": "File Name", "ListViewFieldFileSize": "File Size", + "LoadingTitle": "Processing", + "LoadingSubtitle": "Calculating Existing Files ({0} file(s) found - {1} in total)...", "BottomButtonDeleteAllFiles": "Delete All Files", "BottomButtonDeleteSelectedFiles": "Delete {0} Selected File(s)", "BottomCheckboxFilesSelected": "{0} File(s) Selected ({1} / {2} Total)", From 80027861a49bd800ba36299185163c060d8af844 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 20 Jul 2024 23:47:54 +0700 Subject: [PATCH 03/39] Make sure pkg_version availability before running scan --- .../InstallManagerBase.PkgVersion.cs | 63 ++++++++++++++++++- .../XAMLs/MainApp/Pages/HomePage.xaml.cs | 13 +++- .../Lang/Locale/LangFileCleanupPage.cs | 3 +- Hi3Helper.Core/Lang/en_US.json | 3 +- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 1a0cf7393..6bf654ab3 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -4,6 +4,8 @@ using CollapseLauncher.Pages; using Hi3Helper; using Hi3Helper.Data; +using Hi3Helper.Http; +using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Animation; using System; @@ -150,6 +152,45 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc if (!_uninstallGameProperty.HasValue) throw new NotSupportedException("Clean-up feature for this game is not yet supported!"); + // Initialize and get game state, then get the latest package info + LoadingMessageHelper.SetMessage( + Locale.Lang._FileCleanupPage.LoadingTitle, + Locale.Lang._FileCleanupPage.LoadingSubtitle2); + + using Http client = new Http(); + GameInstallStateEnum gameStateEnum = await _gameVersionManager.GetGameState(); + RegionResourceVersion? packageLatestBase = _gameVersionManager + .GetGameLatestZip(gameStateEnum).FirstOrDefault(); + string? packageExtractBasePath = packageLatestBase?.decompressed_path; + + // Check Fail-safe: Download pkg_version files if not exist + string pkgVersionPath = Path.Combine(_gamePath, "pkg_version"); + if (!string.IsNullOrEmpty(packageExtractBasePath)) + { + // Check Fail-safe: Download main pkg_version file if not exist + string mainPkgVersionUrl = ConverterTool.CombineURLFromString(packageExtractBasePath, + "pkg_version"); + await client.Download(mainPkgVersionUrl, pkgVersionPath, _downloadThreadCount, true); + await client.Merge(default); + + // Check Fail-safe: Download audio pkg_version file if not exist + if (!string.IsNullOrEmpty(_gameAudioLangListPathStatic) && !string.IsNullOrEmpty(packageExtractBasePath)) + { + if (!File.Exists(_gameAudioLangListPathStatic)) + throw new FileNotFoundException("Game does have audio lang index file but does not exist!" + + $" Expecting location: {_gameAudioLangListPathStatic}"); + + await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, + packageExtractBasePath, + client); + } + } + + // Check Fail-safe: If the main pkg_version still not exist, throw! + bool isMainPkgVersionExist = File.Exists(pkgVersionPath); + if (!isMainPkgVersionExist) + throw new FileNotFoundException("Cannot get the file list due to pkg_version file not exist!"); + // Try parse the pkg_versions (including the audio one) List pkgFileInfo = new List(); HashSet pkgFileInfoHashSet = new HashSet(); @@ -183,6 +224,26 @@ await Task.Run(() => } } + protected virtual async ValueTask DownloadOtherAudioPkgVersion(string audioListFilePath, string baseExtractUrl, Http client) + { + // Initialize reader + using StreamReader reader = new StreamReader(audioListFilePath); + // Read until EOF + while (!reader.EndOfStream) + { + // Read the line and skip if it's empty + string? line = await reader.ReadLineAsync(); + if (string.IsNullOrEmpty(line)) continue; + + // Get the pkg_version filename, url and then download it + string pkgFileName = $"Audio_{line.Trim()}_pkg_version"; + string pkgPath = Path.Combine(_gamePath, pkgFileName); + string pkgUrl = ConverterTool.CombineURLFromString(baseExtractUrl, pkgFileName); + await client.Download(pkgUrl, pkgPath, _downloadThreadCount, true); + await client.Merge(default); + } + } + protected virtual async ValueTask ParsePkgVersions2FileInfo(List pkgFileInfo, HashSet pkgFileInfoHashSet, CancellationToken token) { string gamePath = _gamePath; @@ -310,7 +371,7 @@ await Task.Run(() => _parentUI.DispatcherQueue.TryEnqueue(() => LoadingMessageHelper.SetMessage( Locale.Lang._FileCleanupPage.LoadingTitle, - string.Format(Locale.Lang._FileCleanupPage.LoadingSubtitle, + string.Format(Locale.Lang._FileCleanupPage.LoadingSubtitle1, count, ConverterTool.SummarizeSizeSimple(totalSize)) )); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index 78c8f72ca..56700916e 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -1923,9 +1923,16 @@ private void OpenScreenshotFolderButton_Click(object sender, RoutedEventArgs e) private async void CleanupFilesButton_Click(object sender, RoutedEventArgs e) { - GameStartupSetting.Flyout.Hide(); - if (CurrentGameProperty?._GameInstall != null) - await CurrentGameProperty._GameInstall.CleanUpGameFiles(true); + try + { + GameStartupSetting.Flyout.Hide(); + if (CurrentGameProperty?._GameInstall != null) + await CurrentGameProperty._GameInstall.CleanUpGameFiles(true); + } + catch (Exception ex) + { + ErrorSender.SendException(ex); + } } #endregion diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs index 5cef001dd..549a1c0b7 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -14,7 +14,8 @@ public sealed class LangFileCleanupPage public string ListViewFieldFileName { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileName; public string ListViewFieldFileSize { get; set; } = LangFallback?._FileCleanupPage.ListViewFieldFileSize; public string LoadingTitle { get; set; } = LangFallback?._FileCleanupPage.LoadingTitle; - public string LoadingSubtitle { get; set; } = LangFallback?._FileCleanupPage.LoadingSubtitle; + public string LoadingSubtitle1 { get; set; } = LangFallback?._FileCleanupPage.LoadingSubtitle1; + public string LoadingSubtitle2 { get; set; } = LangFallback?._FileCleanupPage.LoadingSubtitle2; public string BottomButtonDeleteAllFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteAllFiles; public string BottomButtonDeleteSelectedFiles { get; set; } = LangFallback?._FileCleanupPage.BottomButtonDeleteSelectedFiles; public string BottomCheckboxFilesSelected { get; set; } = LangFallback?._FileCleanupPage.BottomCheckboxFilesSelected; diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 0e0f36e15..b61cbe1f2 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -1269,7 +1269,8 @@ "ListViewFieldFileName": "File Name", "ListViewFieldFileSize": "File Size", "LoadingTitle": "Processing", - "LoadingSubtitle": "Calculating Existing Files ({0} file(s) found - {1} in total)...", + "LoadingSubtitle1": "Calculating Existing Files ({0} file(s) found - {1} in total)...", + "LoadingSubtitle2": "Checking pkg_version's availability...", "BottomButtonDeleteAllFiles": "Delete All Files", "BottomButtonDeleteSelectedFiles": "Delete {0} Selected File(s)", "BottomCheckboxFilesSelected": "{0} File(s) Selected ({1} / {2} Total)", From 184f51a04d34163bf4ace4a645d406bbde1cc820 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 21 Jul 2024 00:31:36 +0700 Subject: [PATCH 04/39] Do more pkg_version check on cleanup init --- .../InstallManagerBase.PkgVersion.cs | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 6bf654ab3..e00c3b8ba 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -13,6 +13,7 @@ using System.IO; using System.IO.Hashing; using System.Linq; +using System.Net; using System.Security.Cryptography; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -152,44 +153,48 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc if (!_uninstallGameProperty.HasValue) throw new NotSupportedException("Clean-up feature for this game is not yet supported!"); - // Initialize and get game state, then get the latest package info - LoadingMessageHelper.SetMessage( - Locale.Lang._FileCleanupPage.LoadingTitle, - Locale.Lang._FileCleanupPage.LoadingSubtitle2); - - using Http client = new Http(); - GameInstallStateEnum gameStateEnum = await _gameVersionManager.GetGameState(); - RegionResourceVersion? packageLatestBase = _gameVersionManager - .GetGameLatestZip(gameStateEnum).FirstOrDefault(); - string? packageExtractBasePath = packageLatestBase?.decompressed_path; - - // Check Fail-safe: Download pkg_version files if not exist - string pkgVersionPath = Path.Combine(_gamePath, "pkg_version"); - if (!string.IsNullOrEmpty(packageExtractBasePath)) + // Do pkg_version check if Zip Check is used + if (includeZipCheck) { - // Check Fail-safe: Download main pkg_version file if not exist - string mainPkgVersionUrl = ConverterTool.CombineURLFromString(packageExtractBasePath, - "pkg_version"); - await client.Download(mainPkgVersionUrl, pkgVersionPath, _downloadThreadCount, true); - await client.Merge(default); - - // Check Fail-safe: Download audio pkg_version file if not exist - if (!string.IsNullOrEmpty(_gameAudioLangListPathStatic) && !string.IsNullOrEmpty(packageExtractBasePath)) + // Initialize and get game state, then get the latest package info + LoadingMessageHelper.SetMessage( + Locale.Lang._FileCleanupPage.LoadingTitle, + Locale.Lang._FileCleanupPage.LoadingSubtitle2); + + using Http client = new Http(); + GameInstallStateEnum gameStateEnum = await _gameVersionManager.GetGameState(); + RegionResourceVersion? packageLatestBase = _gameVersionManager + .GetGameLatestZip(gameStateEnum).FirstOrDefault(); + string? packageExtractBasePath = packageLatestBase?.decompressed_path; + + // Check Fail-safe: Download pkg_version files if not exist + string pkgVersionPath = Path.Combine(_gamePath, "pkg_version"); + if (!string.IsNullOrEmpty(packageExtractBasePath)) { - if (!File.Exists(_gameAudioLangListPathStatic)) - throw new FileNotFoundException("Game does have audio lang index file but does not exist!" - + $" Expecting location: {_gameAudioLangListPathStatic}"); + // Check Fail-safe: Download main pkg_version file + string mainPkgVersionUrl = ConverterTool.CombineURLFromString(packageExtractBasePath, + "pkg_version"); + await client.Download(mainPkgVersionUrl, pkgVersionPath, _downloadThreadCount, true); + await client.Merge(default); + + // Check Fail-safe: Download audio pkg_version files + if (!string.IsNullOrEmpty(_gameAudioLangListPathStatic) && !string.IsNullOrEmpty(packageExtractBasePath)) + { + if (!File.Exists(_gameAudioLangListPathStatic)) + throw new FileNotFoundException("Game does have audio lang index file but does not exist!" + + $" Expecting location: {_gameAudioLangListPathStatic}"); - await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, - packageExtractBasePath, - client); + await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, + packageExtractBasePath, + client); + } } - } - // Check Fail-safe: If the main pkg_version still not exist, throw! - bool isMainPkgVersionExist = File.Exists(pkgVersionPath); - if (!isMainPkgVersionExist) - throw new FileNotFoundException("Cannot get the file list due to pkg_version file not exist!"); + // Check Fail-safe: If the main pkg_version still not exist, throw! + bool isMainPkgVersionExist = File.Exists(pkgVersionPath); + if (!isMainPkgVersionExist) + throw new FileNotFoundException("Cannot get the file list due to pkg_version file not exist!"); + } // Try parse the pkg_versions (including the audio one) List pkgFileInfo = new List(); @@ -239,6 +244,12 @@ protected virtual async ValueTask DownloadOtherAudioPkgVersion(string audioListF string pkgFileName = $"Audio_{line.Trim()}_pkg_version"; string pkgPath = Path.Combine(_gamePath, pkgFileName); string pkgUrl = ConverterTool.CombineURLFromString(baseExtractUrl, pkgFileName); + + // Skip if URL is not found + if ((await FallbackCDNUtil.GetURLStatusCode(pkgUrl, default)).StatusCode == HttpStatusCode.NotFound) + continue; + + // Download the file await client.Download(pkgUrl, pkgPath, _downloadThreadCount, true); await client.Merge(default); } From 37684439b6ba6ccaef3dccb5ded6790a6c5ea4f9 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 21 Jul 2024 11:47:33 +0700 Subject: [PATCH 05/39] Fix update conflict on metadata and app updates --- .../Classes/Helper/Update/LauncherUpdateHelper.cs | 2 +- CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs index c1c4c28e1..44ab9b118 100644 --- a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs +++ b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs @@ -33,7 +33,7 @@ internal static GameVersion? LauncherCurrentVersion internal static string LauncherCurrentVersionString => _launcherCurrentVersionString; - internal static async void RunUpdateCheckDetached() + internal static async Task RunUpdateCheckDetached() { try { diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index dad402ff1..ed768b7b4 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -587,11 +587,12 @@ private async void RunBackgroundCheck() await SpawnPushAppNotification(); // Check Metadata Update in Background - await CheckMetadataUpdateInBackground(); + if (await CheckMetadataUpdateInBackground()) + return; // Cancel any routine below to avoid conflict with app update #if !DEBUG // Run the update check and trigger routine - LauncherUpdateHelper.RunUpdateCheckDetached(); + await LauncherUpdateHelper.RunUpdateCheckDetached(); #else LogWriteLine("Running debug build, stopping update checks!", LogType.Error); #endif @@ -1102,7 +1103,7 @@ private void GameComboBox_OnDropDownClosed(object sender, object e) #endregion #region Metadata Update Method - private async Task CheckMetadataUpdateInBackground() + private async ValueTask CheckMetadataUpdateInBackground() { bool IsUpdate = await LauncherMetadataHelper.IsMetadataHasUpdate(); if (IsUpdate) @@ -1175,6 +1176,7 @@ private async Task CheckMetadataUpdateInBackground() true ); } + return IsUpdate; } #endregion From 059917627f4e373c8088f9318f03e67c92471a43 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Mon, 22 Jul 2024 12:34:40 +0700 Subject: [PATCH 06/39] Add `Move to Recycle Bin` function for game cleanup --- .../MainApp/Pages/FileCleanupPage.xaml.cs | 26 +++++--- Hi3Helper.Core/Classes/Data/InvokeProp.cs | 62 ++++++++++++++----- .../Lang/Locale/LangFileCleanupPage.cs | 2 + Hi3Helper.Core/Lang/en_US.json | 4 +- 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs index 627b068bd..62c6bcb9d 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs @@ -149,13 +149,16 @@ private async Task PerformRemoval(IList deletionSource, long tota this, Locale.Lang._Misc.NoCancel, Locale.Lang._Misc.YesContinue, - null, + Locale.Lang._FileCleanupPage.DialogMoveToRecycleBin, ContentDialogButton.Close, ContentDialogTheme.Warning); - if (ContentDialogResult.Primary != dialogResult) return; + + int deleteSuccess = 0; + int deleteFailed = 0; + + bool isToRecycleBin = dialogResult == ContentDialogResult.Secondary; + if (dialogResult == ContentDialogResult.None) return; - int deleteSuccess = 0; - int deleteFailed = 0; foreach (LocalFileInfo fileInfo in deletionSource) { try @@ -164,21 +167,28 @@ private async Task PerformRemoval(IList deletionSource, long tota if (fileInfoN.Exists) { fileInfoN.IsReadOnly = false; - fileInfoN.Delete(); + if (isToRecycleBin) + InvokeProp.MoveFileToRecycleBin(fileInfoN.FullName); + else + fileInfoN.Delete(); } FileInfoSource.Remove(fileInfo); - ++deleteSuccess; } catch (Exception ex) { ++deleteFailed; - Logger.LogWriteLine($"Failed while deleting this file: {fileInfo.FullPath}\r\n{ex}", LogType.Error, true); + Logger.LogWriteLine($"Failed while moving this file to recycle bin: {fileInfo.FullPath}\r\n{ex}", + LogType.Error, true); } } - await SimpleDialogs.SpawnDialog(Locale.Lang._FileCleanupPage.DialogDeleteSuccessTitle, + string diagTitle = dialogResult == ContentDialogResult.Primary + ? Locale.Lang._FileCleanupPage.DialogDeleteSuccessTitle + : Locale.Lang._FileCleanupPage.DialogTitleMovedToRecycleBin; + + await SimpleDialogs.SpawnDialog(diagTitle, string.Format(Locale.Lang._FileCleanupPage.DialogDeleteSuccessSubtitle1, deleteSuccess) + (deleteFailed == 0 ? string.Empty : ' ' + string.Format(Locale.Lang._FileCleanupPage.DialogDeleteSuccessSubtitle2, deleteFailed)), diff --git a/Hi3Helper.Core/Classes/Data/InvokeProp.cs b/Hi3Helper.Core/Classes/Data/InvokeProp.cs index 70fdce531..8bdd10179 100644 --- a/Hi3Helper.Core/Classes/Data/InvokeProp.cs +++ b/Hi3Helper.Core/Classes/Data/InvokeProp.cs @@ -82,6 +82,19 @@ private enum ExecutionState : uint EsDisplayRequired = 0x00000002, EsSystemRequired = 0x00000001 } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct SHFILEOPSTRUCT + { + public IntPtr hwnd; + public uint wFunc; + public string pFrom; + public string pTo; + public ushort fFlags; + public int fAnyOperationsAborted; + public IntPtr hNameMappings; + public string lpszProgressTitle; + } #endregion #region Kernel32 @@ -292,6 +305,40 @@ public unsafe static bool IsProcessExist(ReadOnlySpan processName) return false; } #endregion + + #region shell32 + [DllImport("shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern uint ExtractIconEx(string lpszFile, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, uint nIcons); + + public static void SetWindowIcon(IntPtr hWnd, IntPtr hIconLarge, IntPtr hIconSmall) + { + const uint WM_SETICON = 0x0080; + const UIntPtr ICON_BIG = 1; + const UIntPtr ICON_SMALL = 0; + SendMessage(hWnd, WM_SETICON, ICON_BIG, hIconLarge); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, hIconSmall); + } + + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + private static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp); + #endregion + + public static void MoveFileToRecycleBin(string filePath) + { + uint FO_DELETE = 0x0003; + ushort FOF_ALLOWUNDO = 0x0040; + ushort FOF_NOCONFIRMATION = 0x0010; + + SHFILEOPSTRUCT fileOp = new SHFILEOPSTRUCT + { + wFunc = FO_DELETE, + pFrom = filePath + '\0' + '\0', + fFlags = (ushort)(FOF_ALLOWUNDO | FOF_NOCONFIRMATION) + }; + + SHFileOperation(ref fileOp); + } #nullable enable public static CancellationTokenSource? _preventSleepToken; @@ -427,21 +474,6 @@ public InvokePresence(IntPtr windowPtr) public void HideWindow() => ShowWindowAsync(m_WindowPtr, (int)HandleEnum.SW_SHOWMINIMIZED); } - #region shell32 - [DllImport("shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern uint ExtractIconEx(string lpszFile, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, uint nIcons); - - public static void SetWindowIcon(IntPtr hWnd, IntPtr hIconLarge, IntPtr hIconSmall) - { - const uint WM_SETICON = 0x0080; - const UIntPtr ICON_BIG = 1; - const UIntPtr ICON_SMALL = 0; - SendMessage(hWnd, WM_SETICON, ICON_BIG, hIconLarge); - SendMessage(hWnd, WM_SETICON, ICON_SMALL, hIconSmall); - } - #endregion - public delegate bool HandlerRoutine(uint dwCtrlType); public static Process[] GetInstanceProcesses() diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs index 549a1c0b7..875538170 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -29,6 +29,8 @@ public sealed class LangFileCleanupPage public string DialogDeleteSuccessTitle { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessTitle; public string DialogDeleteSuccessSubtitle1 { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessSubtitle1; public string DialogDeleteSuccessSubtitle2 { get; set; } = LangFallback?._FileCleanupPage.DialogDeleteSuccessSubtitle2; + public string DialogMoveToRecycleBin { get; set; } = LangFallback?._FileCleanupPage.DialogMoveToRecycleBin; + public string DialogTitleMovedToRecycleBin { get; set; } = LangFallback?._FileCleanupPage.DialogTitleMovedToRecycleBin; } } #endregion diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index b61cbe1f2..afb397c1e 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -1283,7 +1283,9 @@ "DialogDeletingFileSubtitle5": "Are you sure to delete the files?", "DialogDeleteSuccessTitle": "Files Deleted!", "DialogDeleteSuccessSubtitle1": "{0} file(s) have been successfully deleted", - "DialogDeleteSuccessSubtitle2": "with {0} failed to delete" + "DialogDeleteSuccessSubtitle2": "with {0} failed to delete", + "DialogMoveToRecycleBin": "Move to Recycle Bin", + "DialogTitleMovedToRecycleBin": "Files moved to Recycle Bin!" }, "_OOBEStartUpMenu": { From ec876b5d4a00b3ac360a0b7159cec2150c5370a9 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Mon, 22 Jul 2024 19:06:22 +0700 Subject: [PATCH 07/39] Add finer control for Sophon download mode - Added global toggle for Sophon enablement > Other means of disabling Sophon should work still - Separated Sophon Download thread from regular method > Default visible value is 0, clamped at 2-64. > Auto at 0 uses sqrt of thread# - Separated Sophon http connection limit from regular method > Default visible value is 0, clamped at 4-128. > Auto at 0 uses sqrt of (thread#*2) > Use download thread count if value is lower Other adjustment: - Clamp maxChunkThread at 2-32 with value from maxThread/2 Added help text adjustments from @Cryotechnic and JP translation from @Vermilion-Sinsha Co-authored-by: Ron Friedman <9833218+Cryotechnic@users.noreply.github.com> Co-authored-by: Vermilion-Sinsha <131636335+Vermilion-Sinsha@users.noreply.github.com> --- .../BaseClass/InstallManagerBase.cs | 33 +++++-- .../XAMLs/MainApp/Pages/SettingsPage.xaml | 96 +++++++++++++++++++ .../XAMLs/MainApp/Pages/SettingsPage.xaml.cs | 18 ++++ .../Classes/Shared/Region/LauncherConfig.cs | 5 +- .../Lang/Locale/LangSettingsPage.cs | 8 ++ Hi3Helper.Core/Lang/en_US.json | 9 ++ Hi3Helper.Core/Lang/ja_JP.json | 7 ++ 7 files changed, 166 insertions(+), 10 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 382f014c3..757268708 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -148,8 +148,25 @@ protected bool _isSophonPreloadCompleted public virtual bool IsUseSophon => _gameVersionManager.GamePreset.LauncherResourceChunksURL != null && !File.Exists(Path.Combine(_gamePath, "@DisableSophon")) - && (!_canDeltaPatch && !_forceIgnoreDeltaPatch); + && (!_canDeltaPatch && !_forceIgnoreDeltaPatch) + && LauncherConfig.GetAppConfigValue("IsEnableSophon").ToBool(); + protected virtual int SophonGetThreadNum() + { + // Get from config + var n = LauncherConfig.GetAppConfigValue("SophonCpuThread").ToInt(); + if (n == 0) // If config is default "0", then use sqrt of thread number as safe number + n = (int)Math.Sqrt(Environment.ProcessorCount); + return Math.Clamp(n, 2, 64); // Clamp value to prevent errors + } + + protected virtual int SophonGetHttpHandler() + { + var n = LauncherConfig.GetAppConfigValue("SophonHttpConnInt").ToInt(); + if (n == 0) + n = (int)Math.Sqrt(Environment.ProcessorCount) * 2; + return Math.Clamp(n, 4, 128); + } #endregion public InstallManagerBase(UIElement parentUI, IGameVersionCheck GameVersionManager) @@ -637,17 +654,15 @@ public virtual async ValueTask StartPackageVerification(List Math.Clamp((int)Math.Sqrt(_threadCount), 2, 64); - public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameState) { // Set the flag to false _isSophonDownloadCompleted = false; // Set the max thread and httpHandler based on settings - int maxThread = SophonGetThreadNum; - int maxChunksThread = Math.Min(_threadCount / 2, 4); - int maxHttpHandler = Math.Max(maxThread * maxChunksThread, _downloadThreadCount); + int maxThread = SophonGetThreadNum(); + int maxChunksThread = Math.Clamp(maxThread / 2, 2, 32); + int maxHttpHandler = Math.Max(maxThread, SophonGetHttpHandler()); LogWriteLine($"Initializing Sophon Chunk download method with Main Thread: {maxThread}, Chunks Thread: {maxChunksThread} and Max HTTP handle: {maxHttpHandler}", LogType.Default, true); @@ -832,9 +847,9 @@ public virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameStat _isSophonDownloadCompleted = false; // Set the max thread and httpHandler based on settings - int maxThread = SophonGetThreadNum; - int maxChunksThread = Math.Min(_threadCount / 2, 4); - int maxHttpHandler = Math.Max(maxThread * maxChunksThread, _downloadThreadCount); + int maxThread = SophonGetThreadNum(); + int maxChunksThread = Math.Clamp(maxThread / 2, 2, 32); + int maxHttpHandler = Math.Max(maxThread, SophonGetHttpHandler()); LogWriteLine($"Initializing Sophon Chunk update method with Main Thread: {maxThread}, Chunks Thread: {maxChunksThread} and Max HTTP handle: {maxHttpHandler}", LogType.Default, true); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml index 205d046a2..c7cb22463 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml @@ -472,6 +472,102 @@ IsOn="{x:Bind IsUseDownloadChunksMerging, Mode=TwoWay}" OffContent="{x:Bind helper:Locale.Lang._Misc.Disabled}" OnContent="{x:Bind helper:Locale.Lang._Misc.Enabled}" /> + + + + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs index a9c5d86d6..e3224fbd7 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs @@ -991,6 +991,24 @@ private bool IsStartupToTray task.Dispose(); } } + + private bool IsEnableSophon + { + get => GetAppConfigValue("IsEnableSophon").ToBool(); + set => SetAndSaveConfigValue("IsEnableSophon", value); + } + + private int SophonDownThread + { + get => GetAppConfigValue("SophonCpuThread").ToInt(); + set => SetAndSaveConfigValue("SophonCpuThread", value); + } + + private int SophonHttpConn + { + get => GetAppConfigValue("SophonHttpConnInt").ToInt(); + set => SetAndSaveConfigValue("SophonHttpConnInt", value); + } #endregion #region Keyboard Shortcuts diff --git a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs index 9fdb9ba9f..3e49ca57c 100644 --- a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs +++ b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs @@ -364,7 +364,10 @@ public static Guid GetGuid(int sessionNum) { "BackgroundAudioVolume", 0.5d }, { "BackgroundAudioIsMute", true }, { "UseInstantRegionChange", true }, - { "IsIntroEnabled", true } + { "IsIntroEnabled", true }, + { "IsEnableSophon", true}, + { "SophonCpuThread", 0}, + { "SophonHttpConnInt", 0} }; #endregion } diff --git a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs index 83ea80089..77f9217d9 100644 --- a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs @@ -110,6 +110,14 @@ public sealed class LangSettingsPage public string Waifu2X_Warning_D3DMappingLayers { get; set; } = LangFallback?._SettingsPage.Waifu2X_Warning_D3DMappingLayers; public string Waifu2X_Error_Loader { get; set; } = LangFallback?._SettingsPage.Waifu2X_Error_Loader; public string Waifu2X_Error_Output { get; set; } = LangFallback?._SettingsPage.Waifu2X_Error_Output; + public string SophonSettingsTitle { get; set; } = LangFallback?._SettingsPage.SophonSettingsTitle; + public string SophonHelp_Title { get; set; } = LangFallback?._SettingsPage.SophonHelp_Title; + public string SophonHelp_1 { get; set; } = LangFallback?._SettingsPage.SophonHelp_1; + public string SophonHelp_2 { get; set; } = LangFallback?._SettingsPage.SophonHelp_2; + public string SophonHelp_Thread { get; set; } = LangFallback?._SettingsPage.SophonHelp_Thread; + public string SophonHttpNumberBox { get; set; } = LangFallback?._SettingsPage.SophonHttpNumberBox; + public string SophonHelp_Http { get; set; } = LangFallback?._SettingsPage.SophonHelp_Http; + public string SophonToggle { get; set; } = LangFallback?._SettingsPage.SophonToggle; } } #endregion diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index afb397c1e..a7183cc02 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -427,6 +427,15 @@ "AppThreads_Help4": "0 (Auto-detect)", "AppThreads_Help5": "This thread will handle the extraction/verification process while installing/repairing the game.", "AppThreads_Help6": "Note: This setting is no longer effective while installing Honkai Impact 3rd.", + + "SophonSettingsTitle": "Sophon Mode Settings", + "SophonHelp_Title": "What is Sophon Downloader Mode?", + "SophonHelp_1": "\"Sophon\" is a new download mechanism introduced by HoYoVerse recently and allows files to be downloaded into \"chunks\" rather than downloading a big ZIP archive. The benefits are mainly a lower drive space requirement and increased efficiency for downloads and updates.", + "SophonHelp_2": "This method might be slower for those who uses Hard Drives for their game.", + "SophonHelp_Thread": "This controls number of simultaneous chunks download Collapse is doing. Lower this value if you get sudden stuck when downloading.", + "SophonHttpNumberBox": "Maximum HTTP Connections", + "SophonHelp_Http": "This controls maximum number of network connection being established by Collapse to download the chunks.", + "SophonToggle": "Enable Sophon on Supported Regions", "AppThreads_Attention": "Attention", "AppThreads_Attention1": "Before you change the", diff --git a/Hi3Helper.Core/Lang/ja_JP.json b/Hi3Helper.Core/Lang/ja_JP.json index a6874a507..ae088c0de 100644 --- a/Hi3Helper.Core/Lang/ja_JP.json +++ b/Hi3Helper.Core/Lang/ja_JP.json @@ -428,6 +428,13 @@ "AppThreads_Help5": "このスレッドはゲームのインストール・修復中の解凍・整合性チェックプロセスを操作します。", "AppThreads_Help6": "注: 崩壊3rdのインストール中は、この設定が無効になります。", + "SophonSettingsTitle": "Sophon Mode Settings", + "SophonHelp_Title": "Sophonモードって何?", + "SophonHelp_1": "Sophonとは、HoYoVerseが新しく導入したダウンロードシステムです。Sophonモードでは巨大なzipファイルをダウンロードする代わりに、ファイルを小さなチャンクに分割してダウンロードします。このモードを使うと必要なドライブの空き容量が減り、ダウンロードと更新がより効率的になります。", + "SophonHelp_Thread": "Collapseが同時にダウンロードするチャンクの数を設定します。ゲームのダウンロード中にCollapseがクラッシュする場合は、この値を小さくしてください。", + "SophonHttpNumberBox": "最大HTTP接続数", + "SophonHelp_Http": "CollapseがチャンクをダウンロードするためにHoYoVerseのサーバーに対して確立するネットワーク接続の最大数を設定します。", + "AppThreads_Attention": "注意", "AppThreads_Attention1": "既にダウンロード済みのパッケージがある場合は、", "AppThreads_Attention2": "の値を", From 2a6a641dfa317c8705afb6f8cb6ddf23c8062813 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Fri, 26 Jul 2024 16:35:34 +0700 Subject: [PATCH 08/39] [Feat] Add advanced option to ignore certain files from cleanup For both Game Cleanup and Hi3's Repair checks, add a function to ignore certain files from being deleted. User can make a file in the root of gamePath named `@IgnoredFiles` then name the filename (not path) those do not want to get deleted from those mechanisms. Multiple files can be stated with linebreak. Does not support wildcard. Example file content: ``` UnityPlayer.dll VO_4.1_0.pck ``` --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 18 +++++++++++++++++- .../Classes/RepairManagement/Honkai/Check.cs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index e00c3b8ba..ff3c7d26c 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -19,6 +19,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using static Hi3Helper.Logger; #nullable enable namespace CollapseLauncher.InstallManager.Base @@ -200,6 +201,20 @@ await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, List pkgFileInfo = new List(); HashSet pkgFileInfoHashSet = new HashSet(); await ParsePkgVersions2FileInfo(pkgFileInfo, pkgFileInfoHashSet, _token.Token); + + string[] ignoredFiles = []; + if (File.Exists(Path.Combine(_gamePath, "@IgnoredFiles"))) + { + try + { + ignoredFiles = File.ReadAllLines(Path.Combine(_gamePath, "@IgnoredFiles")); + LogWriteLine("Found ignore file settings!"); + } + catch (Exception ex) + { + LogWriteLine($"Failed when reading ignore file setting! Ignoring...\r\n{ex}", LogType.Error, true); + } + } // Get the list of the local file paths List localFileInfo = new List(); @@ -216,7 +231,8 @@ await Task.Run(() => { lock (unusedFileInfo) { - unusedFileInfo.Add(asset); + if (!ignoredFiles.Contains(asset.ToFileInfo().Name, StringComparer.OrdinalIgnoreCase)) + unusedFileInfo.Add(asset); } } })); diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs index 1672fedbc..29270335c 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs @@ -622,6 +622,22 @@ private void GetUnusedAssetIndexList(List catalog, List 0) _ignoredUnusedFileList.AddRange(ignoredFiles); + // Is file ignored bool isFileIgnored = _ignoredUnusedFileList.Contains(asset, StringComparer.OrdinalIgnoreCase); From e9fa4b6f44ca0a1f6c1c8f02541d79e9b810740a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:54:33 +0000 Subject: [PATCH 09/39] [skip ci] Sync translation Translate en_US.json in zh_CN 100% reviewed source file: 'en_US.json' on 'zh_CN'. --- Hi3Helper.Core/Lang/zh_CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Hi3Helper.Core/Lang/zh_CN.json b/Hi3Helper.Core/Lang/zh_CN.json index 50bb0408e..b58e10bd5 100644 --- a/Hi3Helper.Core/Lang/zh_CN.json +++ b/Hi3Helper.Core/Lang/zh_CN.json @@ -1255,7 +1255,8 @@ "TW/HK/MO": "港澳台", "Korea": "韩国", "Japan": "日本", - "Bilibili": "哔哩哔哩" + "Bilibili": "哔哩哔哩", + "Google Play": "Google Play" }, "_OOBEAgreementMenu": { From 26d0af8335c7efc555831c85dd6a61a10e0f534a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:23:50 +0000 Subject: [PATCH 10/39] [skip ci] Sync translation Translate en_US.json in id_ID 100% reviewed source file: 'en_US.json' on 'id_ID'. --- Hi3Helper.Core/Lang/id_ID.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Hi3Helper.Core/Lang/id_ID.json b/Hi3Helper.Core/Lang/id_ID.json index 3d530909e..ba2476551 100644 --- a/Hi3Helper.Core/Lang/id_ID.json +++ b/Hi3Helper.Core/Lang/id_ID.json @@ -174,7 +174,11 @@ "CommunityToolsBtn_CommunityText": "Alat Komunitas", "CommunityToolsBtn_OpenExecutableAppDialogTitle": "Pilih Executable Program: {0}", - "CreateShortcut_FolderPicker": "Pilih tempat untuk menyimpan pintasan" + "CreateShortcut_FolderPicker": "Pilih tempat untuk menyimpan pintasan", + + "Exception_DownloadTimeout1": "Masa tenggang telah terjadi saat mencoba untuk memasang {0}", + "Exception_DownloadTimeout2": "Pastikan internet kamu stabil! Coba turunkan thread unduhan apabila kamu punya koneksi lambat.", + "Exception_DownloadTimeout3": "**PERINGATAN** Mengubah thread unduhan akan memulai ulang unduhanmu dari 0 dan kamu harus menghapus chunk dari unduhan sebelumnya secara manual!" }, "_GameRepairPage": { @@ -1251,7 +1255,8 @@ "TW/HK/MO": "Tiongkok Tradisional", "Korea": "Korea", "Japan": "Jepang", - "Bilibili": "Bilibili" + "Bilibili": "Bilibili", + "Google Play": "Google Play" }, "_OOBEAgreementMenu": { From 5e761714cc5003c80d0c703397bc03fc4c12b354 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Fri, 26 Jul 2024 22:14:36 +0700 Subject: [PATCH 11/39] Raise MainPage's Subscriber Events at Top --- CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index ed768b7b4..1b25f089e 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -109,6 +109,7 @@ private void Page_Unloaded(object sender, RoutedEventArgs e) private async void StartRoutine(object sender, RoutedEventArgs e) { + SubscribeEvents(); try { if (!IsShowRegionChangeWarning && IsInstantRegionChange) @@ -143,7 +144,6 @@ private async void StartRoutine(object sender, RoutedEventArgs e) if (WindowUtility.CurrentWindow is MainWindow) m_actualMainFrameSize = new Size((float)WindowUtility.CurrentWindow.Bounds.Width, (float)WindowUtility.CurrentWindow.Bounds.Height); - SubscribeEvents(); ChangeTitleDragArea.Change(DragAreaTemplate.Default); await InitializeStartup(); From 575e12db28a92bd555e0737d4e2d7ec902a2720c Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 27 Jul 2024 18:13:18 +0700 Subject: [PATCH 12/39] FE & BE Adjustments for Sophon This one is a hella big works --- CollapseLauncher/App.xaml | 5 +- .../Classes/CachesManagement/Honkai/Check.cs | 14 +- .../Classes/CachesManagement/Honkai/Fetch.cs | 8 +- .../CachesManagement/Honkai/HonkaiCache.cs | 2 +- .../Classes/CachesManagement/Honkai/Update.cs | 20 +- .../CachesManagement/StarRail/Check.cs | 12 +- .../CachesManagement/StarRail/Fetch.cs | 8 +- .../StarRail/StarRailCache.cs | 2 +- .../CachesManagement/StarRail/Update.cs | 20 +- .../BackgroundActivityManager.cs | 10 +- .../BaseClass/InstallManagerBase.cs | 277 +++++++++--------- .../Interfaces/Class/GamePropertyBase.cs | 1 + .../Classes/Interfaces/Class/ProgressBase.cs | 268 ++++++++++------- .../Classes/Interfaces/Class/Structs.cs | 55 ++-- .../Classes/Interfaces/IGameInstallManager.cs | 1 + .../Classes/RepairManagement/Genshin/Check.cs | 24 +- .../Classes/RepairManagement/Genshin/Fetch.cs | 6 +- .../RepairManagement/Genshin/GenshinRepair.cs | 2 +- .../RepairManagement/Genshin/Repair.cs | 6 +- .../Classes/RepairManagement/Honkai/Check.cs | 64 ++-- .../Classes/RepairManagement/Honkai/Fetch.cs | 14 +- .../RepairManagement/Honkai/HonkaiRepair.cs | 2 +- .../Classes/RepairManagement/Honkai/Repair.cs | 14 +- .../RepairManagement/StarRail/Check.cs | 32 +- .../RepairManagement/StarRail/Fetch.cs | 10 +- .../RepairManagement/StarRail/Repair.cs | 6 +- .../StarRail/StarRailRepair.cs | 2 +- .../XAMLs/MainApp/Pages/CachesPage.xaml.cs | 6 +- .../MainApp/Pages/Dialogs/SimpleDialogs.cs | 2 +- .../XAMLs/MainApp/Pages/HomePage.xaml | 215 ++++++++++++++ .../XAMLs/MainApp/Pages/HomePage.xaml.cs | 146 ++++++--- .../XAMLs/MainApp/Pages/RepairPage.xaml.cs | 6 +- Hi3Helper.Core/Lang/Locale/LangMisc.cs | 3 + Hi3Helper.Core/Lang/en_US.json | 3 + 34 files changed, 810 insertions(+), 456 deletions(-) diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml index ce4f9985d..fc50603c9 100644 --- a/CollapseLauncher/App.xaml +++ b/CollapseLauncher/App.xaml @@ -116,8 +116,9 @@ TintOpacity="0.0"/> + TintColor="#222222" + TintOpacity="0.2" + TintLuminosityOpacity="0.2"/> > Check(List assetIndex, Cancella List returnAsset = new List(); // Set Indetermined status as false - _status!.IsProgressTotalIndetermined = false; + _status!.IsProgressAllIndetermined = false; // Show the asset entry panel _status.IsAssetEntryPanelShow = true; @@ -72,7 +72,7 @@ private void CheckUnusedAssets(List assetIndex, List ret && !assetIndex!.Exists(x => x!.ConcatPath == filePath)) { // Increment the total found count - _progressTotalCountFound++; + _progressAllCountFound++; // Add asset to the returnAsset FileInfo fileInfo = new FileInfo(filePath); @@ -107,9 +107,9 @@ private void CheckUnusedAssets(List assetIndex, List ret private async ValueTask CheckAsset(CacheAsset asset, List returnAsset, CancellationToken token) { // Increment the count and update the status - Interlocked.Add(ref _progressTotalCountCurrent, 1); + Interlocked.Add(ref _progressAllCountCurrent, 1); _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusChecking!, asset!.DataType, asset.N); - _status!.ActivityTotal = string.Format(Lang._CachesPage.CachesTotalStatusChecking!, _progressTotalCountCurrent, _progressTotalCount); + _status!.ActivityAll = string.Format(Lang._CachesPage.CachesTotalStatusChecking!, _progressAllCountCurrent, _progressAllCountTotal); // Assign the file info. FileInfo fileInfo = new FileInfo(asset.ConcatPath!); @@ -154,9 +154,9 @@ private void AddGenericCheckAsset(CacheAsset asset, CacheAssetStatus assetStatus lock (this) { // Set Indetermined status as false - _status!.IsProgressTotalIndetermined = false; - _progressTotalCountFound++; - _progressTotalSizeFound += asset!.CS; + _status!.IsProgressAllIndetermined = false; + _progressAllCountFound++; + _progressAllSizeFound += asset!.CS; } // Add file into asset index diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index 93e170038..85aeb6cac 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -61,8 +61,8 @@ private async Task> Fetch(CancellationToken token) LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); // Increment the Total Size and Count - _progressTotalCount += count.Item1; - _progressTotalSize += count.Item2; + _progressAllCountTotal += count.Item1; + _progressAllSizeTotal += count.Item2; } } finally @@ -122,7 +122,7 @@ private async Task BuildGameRepoURL(CancellationToken token) { // Set total activity string as "Fetching Caches Type: " _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, type); - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; _status.IsIncludePerFileIndicator = false; UpdateStatus(); @@ -268,7 +268,7 @@ await Parallel.ForEachAsync(EnumerateCacheTextAsset(type, dataTextAsset.GetStrin { // Update the status _status!.ActivityStatus = string.Format(Lang._CachesPage.Status2, type, content.N); - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.IsProgressPerFileIndetermined = true; UpdateStatus(); diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/HonkaiCache.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/HonkaiCache.cs index c7ce4d8d9..9f2616eb1 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/HonkaiCache.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/HonkaiCache.cs @@ -62,7 +62,7 @@ private async Task CheckRoutine() // either way, returns false. return SummarizeStatusAndProgress( _updateAssetIndex, - string.Format(Lang!._CachesPage!.CachesStatusNeedUpdate!, _progressTotalCountFound, ConverterTool.SummarizeSizeSimple(_progressTotalSizeFound)), + string.Format(Lang!._CachesPage!.CachesStatusNeedUpdate!, _progressAllCountFound, ConverterTool.SummarizeSizeSimple(_progressAllSizeFound)), Lang._CachesPage.CachesStatusUpToDate); } diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index 178964ba8..bfd881fa0 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -20,8 +20,8 @@ private async Task Update(List updateAssetIndex, List assetIndex) private async Task UpdateCacheAsset(CacheAsset asset, Http httpClient, CancellationToken token) { // Increment total count and update the status - _progressTotalCountCurrent++; + _progressAllCountCurrent++; _status!.ActivityStatus = string.Format(Lang!._Misc!.Downloading + " {0}: {1}", asset!.DataType, asset.N); UpdateAll(); @@ -130,22 +130,22 @@ private async Task UpdateCacheAsset(CacheAsset asset, Http httpClient, Cancellat private async void _httpClient_UpdateAssetProgress(object sender, DownloadEvent e) { // Update current progress percentages and speed - _progress!.ProgressTotalPercentage = _progressTotalSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressTotalSizeCurrent, _progressTotalSize) : + _progress!.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : 0; if (e!.State != DownloadState.Merging) { - _progressTotalSizeCurrent += e.Read; + _progressAllSizeCurrent += e.Read; } - long speed = (long)(_progressTotalSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); + long speed = (long)(_progressAllSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); if (await CheckIfNeedRefreshStopwatch()) { // Update current activity status - _status!.IsProgressTotalIndetermined = false; - string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, ((_progressTotalSizeCurrent - _progressTotalSize) / ConverterTool.Unzeroed(speed)).ToTimeSpanNormalized()); - _status.ActivityTotal = string.Format(Lang!._Misc!.Downloading + ": {0}/{1} ", _progressTotalCountCurrent, _progressTotalCount) + _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}"; diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Check.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Check.cs index 6ebdefa6b..61f8f47a1 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Check.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Check.cs @@ -20,7 +20,7 @@ private async Task> Check(List assetIndex, CancellationTo List returnAsset = new List(); // Set Indetermined status as false - _status!.IsProgressTotalIndetermined = false; + _status!.IsProgressAllIndetermined = false; // Show the asset entry panel _status.IsAssetEntryPanelShow = true; @@ -80,9 +80,9 @@ private async ValueTask CheckAsset(SRAsset asset, List returnAsset, str // Increment the count and update the status lock (this) { - _progressTotalCountCurrent++; + _progressAllCountCurrent++; _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusChecking!, asset!.AssetType, asset.LocalName); - _status.ActivityTotal = string.Format(Lang!._CachesPage!.CachesTotalStatusChecking!, _progressTotalCountCurrent, _progressTotalCount); + _status.ActivityAll = string.Format(Lang!._CachesPage!.CachesTotalStatusChecking!, _progressAllCountCurrent, _progressAllCountTotal); } // Get persistent and streaming paths @@ -127,9 +127,9 @@ private void AddGenericCheckAsset(SRAsset asset, CacheAssetStatus assetStatus, L lock (this) { // Set Indetermined status as false - _status!.IsProgressTotalIndetermined = false; - _progressTotalCountFound++; - _progressTotalSizeFound += asset!.Size; + _status!.IsProgressAllIndetermined = false; + _progressAllCountFound++; + _progressAllSizeFound += asset!.Size; } // Add file into asset index diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs index b489709ba..68e83db65 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Fetch.cs @@ -28,7 +28,7 @@ private async Task> Fetch(CancellationToken token) // Initialize metadata // Set total activity string as "Fetching Caches Type: Dispatcher" _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, CacheAssetType.Dispatcher); - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.IsIncludePerFileIndicator = false; UpdateStatus(); @@ -58,8 +58,8 @@ private async Task> Fetch(CancellationToken token) LogWriteLine($" Cache Size = {SummarizeSizeSimple(count.Item2)}", LogType.NoTag, true); // Increment the Total Size and Count - _progressTotalCount += count.Item1; - _progressTotalSize += count.Item2; + _progressAllCountTotal += count.Item1; + _progressAllSizeTotal += count.Item2; } } finally @@ -76,7 +76,7 @@ private async Task> Fetch(CancellationToken token) { // Set total activity string as "Fetching Caches Type: " _status!.ActivityStatus = string.Format(Lang!._CachesPage!.CachesStatusFetchingType!, type); - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.IsIncludePerFileIndicator = false; UpdateStatus(); diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/StarRailCache.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/StarRailCache.cs index 78bc1340a..c69a1d9f2 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/StarRailCache.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/StarRailCache.cs @@ -54,7 +54,7 @@ private async Task CheckRoutine() // either way, returns false. return SummarizeStatusAndProgress( _updateAssetIndex, - string.Format(Lang._CachesPage.CachesStatusNeedUpdate, _progressTotalCountFound, ConverterTool.SummarizeSizeSimple(_progressTotalSizeFound)), + string.Format(Lang._CachesPage.CachesStatusNeedUpdate, _progressAllCountFound, ConverterTool.SummarizeSizeSimple(_progressAllSizeFound)), Lang._CachesPage.CachesStatusUpToDate); } diff --git a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs index 11b496b62..bd7b9172a 100644 --- a/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/StarRail/Update.cs @@ -23,8 +23,8 @@ private async Task Update(List updateAssetIndex, List as Http httpClient = new Http(true, 5, 1000, _userAgent); try { - // Set IsProgressTotalIndetermined as false and update the status - _status.IsProgressTotalIndetermined = true; + // Set IsProgressAllIndetermined as false and update the status + _status.IsProgressAllIndetermined = true; UpdateStatus(); // Subscribe the event listener @@ -55,7 +55,7 @@ private async Task Update(List updateAssetIndex, List as private async Task UpdateCacheAsset(SRAsset asset, Http httpClient, CancellationToken token) { // Increment total count and update the status - _progressTotalCountCurrent++; + _progressAllCountCurrent++; _status.ActivityStatus = string.Format(Lang._Misc.Downloading + " {0}: {1}", asset.AssetType, Path.GetFileName(asset.LocalName)); UpdateAll(); @@ -88,22 +88,22 @@ private async Task UpdateCacheAsset(SRAsset asset, Http httpClient, Cancellation private async void _httpClient_UpdateAssetProgress(object sender, DownloadEvent e) { // Update current progress percentages and speed - _progress.ProgressTotalPercentage = _progressTotalSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressTotalSizeCurrent, _progressTotalSize) : + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : 0; if (e.State != DownloadState.Merging) { - _progressTotalSizeCurrent += e.Read; + _progressAllSizeCurrent += e.Read; } - long speed = (long)(_progressTotalSizeCurrent / _stopwatch.Elapsed.TotalSeconds); + long speed = (long)(_progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds); if (await CheckIfNeedRefreshStopwatch()) { // Update current activity status - _status.IsProgressTotalIndetermined = false; - string timeLeftString = string.Format(Lang._Misc.TimeRemainHMSFormat, ((_progressTotalSizeCurrent - _progressTotalSize) / ConverterTool.Unzeroed(speed)).ToTimeSpanNormalized()); - _status.ActivityTotal = string.Format(Lang._Misc.Downloading + ": {0}/{1} ", _progressTotalCountCurrent, _progressTotalCount) + _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}"; diff --git a/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs b/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs index 2d0a647a7..e002db703 100644 --- a/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs +++ b/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs @@ -177,15 +177,15 @@ private static void AttachEventToNotification(int hashID, IBackgroundActivity ac EventHandler ProgressChangedEventHandler = (_, args) => activity?.Dispatch(() => { - progressBar.Value = args!.ProgressTotalPercentage; - progressLeftSubtitle.Text = string.Format(Lang._Misc!.Speed!, ConverterTool.SummarizeSizeSimple(args.ProgressTotalSpeed)); - progressRightTitle.Text = string.Format(Lang._Misc!.TimeRemainHMSFormat!, args.ProgressTotalTimeLeft); - progressRightSubtitle.Text = string.Format(Lang._UpdatePage!.UpdateHeader1! + " {0}%", args.ProgressTotalPercentage); + progressBar.Value = args!.ProgressAllPercentage; + progressLeftSubtitle.Text = string.Format(Lang._Misc!.Speed!, ConverterTool.SummarizeSizeSimple(args.ProgressAllSpeed)); + progressRightTitle.Text = string.Format(Lang._Misc!.TimeRemainHMSFormat!, args.ProgressAllTimeLeft); + progressRightSubtitle.Text = string.Format(Lang._UpdatePage!.UpdateHeader1! + " {0}%", args.ProgressAllPercentage); }); EventHandler StatusChangedEventHandler = (_, args) => activity?.Dispatch(() => { - progressBar.IsIndeterminate = args!.IsProgressTotalIndetermined; + progressBar.IsIndeterminate = args!.IsProgressAllIndetermined; progressLeftTitle.Text = args.ActivityStatus; if (args.IsCanceled) { diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 757268708..6ad8baec7 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -167,6 +167,7 @@ protected virtual int SophonGetHttpHandler() n = (int)Math.Sqrt(Environment.ProcessorCount) * 2; return Math.Clamp(n, 4, 128); } + public virtual bool IsSophonInUpdateMode { get => _isSophonInUpdateMode; } #endregion public InstallManagerBase(UIElement parentUI, IGameVersionCheck GameVersionManager) @@ -253,7 +254,7 @@ protected virtual async ValueTask ConfirmDeltaPatchDialog(DeltaPatchPropert // Set the activity _status!.ActivityStatus = string.Format(Lang!._GameRepairPage!.Status2!); _status!.IsIncludePerFileIndicator = false; - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; UpdateStatus(); // Start the check routine and get the state if download needed @@ -262,9 +263,9 @@ protected virtual async ValueTask ConfirmDeltaPatchDialog(DeltaPatchPropert if (isDownloadNeeded) { _status!.ActivityStatus = string.Format(Lang._GameRepairPage.Status8!, "").Replace(": ", ""); - _progressTotalSizeCurrent = 0; - _progressTotalCountCurrent = 1; - _progressTotalReadCurrent = 0; + _progressAllSizeCurrent = 0; + _progressAllCountCurrent = 1; + _progressAllIOReadCurrent = 0; UpdateStatus(); // If download needed, then start the repair (download) routine @@ -311,11 +312,11 @@ protected virtual async ValueTask StartDeltaPatch(IRepairAssetIndex repair // Get the sum of uncompressed size and // Set progress count to beginning - _progressTotalSize = localAssetIndex!.Sum(x => x!.S); - _progressTotalSizeCurrent = 0; - _progressTotalCountCurrent = 1; + _progressAllSizeTotal = localAssetIndex!.Sum(x => x!.S); + _progressAllSizeCurrent = 0; + _progressAllCountCurrent = 1; _status!.IsIncludePerFileIndicator = false; - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.ActivityStatus = Lang!._Misc!.ApplyingPatch; UpdateStatus(); RestartStopwatch(); @@ -440,18 +441,18 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List x!.Segments != null ? x.Segments.Count : 1) > 1; _status.IsProgressPerFileIndetermined = true; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; UpdateStatus(); // Start getting the size of the packages await GetPackagesRemoteSize(gamePackage, _token!.Token); // Get the remote total size and current total size - _progressTotalSize = gamePackage.Sum(x => x!.Size); - _progressTotalSizeCurrent = GetExistingDownloadPackageSize(gamePackage); + _progressAllSizeTotal = gamePackage.Sum(x => x!.Size); + _progressAllSizeCurrent = GetExistingDownloadPackageSize(gamePackage); // Sanitize Check: Check for the free space of the drive and show the dialog if necessary - await CheckDriveFreeSpace(_parentUI, gamePackage, _progressTotalSizeCurrent); + await CheckDriveFreeSpace(_parentUI, gamePackage, _progressAllSizeCurrent); // Check for the existing download await CheckExistingDownloadAsync(_parentUI, gamePackage); @@ -459,14 +460,14 @@ protected virtual async ValueTask StartDeltaPatchPreReqDownload(List x!.Segments != null ? x.Segments.Count : 1) > 1; _status!.IsProgressPerFileIndetermined = true; - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; UpdateStatus(); // Start getting the size of the packages await GetPackagesRemoteSize(_assetIndex, _token!.Token); // Get the remote total size and current total size - _progressTotalSize = _assetIndex!.Sum(x => x!.Size); - _progressTotalSizeCurrent = GetExistingDownloadPackageSize(_assetIndex); + _progressAllSizeTotal = _assetIndex!.Sum(x => x!.Size); + _progressAllSizeCurrent = GetExistingDownloadPackageSize(_assetIndex); // Sanitize Check: Check for the free space of the drive and show the dialog if necessary - await CheckDriveFreeSpace(_parentUI, _assetIndex, _progressTotalSizeCurrent); + await CheckDriveFreeSpace(_parentUI, _assetIndex, _progressAllSizeCurrent); // Sanitize Check: Show dialog for resuming/reset the existing download if (!skipDialog) @@ -612,16 +613,16 @@ public virtual async ValueTask StartPackageVerification(List x!.Size); - _progressTotalSizeCurrent = 0; - _progressTotalCountCurrent = 1; - _progressTotalCount = assetCount; + _progressAllSizeTotal = gamePackage.Sum(x => x!.Size); + _progressAllSizeCurrent = 0; + _progressAllCountCurrent = 1; + _progressAllCountTotal = assetCount; _status!.IsIncludePerFileIndicator = assetCount > 1; RestartStopwatch(); // Set progress bar to not indetermined _status!.IsProgressPerFileIndetermined = false; - _status!.IsProgressTotalIndetermined = false; + _status!.IsProgressAllIndetermined = false; // Iterate the asset foreach (GameInstallPackage asset in gamePackage) @@ -681,7 +682,7 @@ public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameSta try { // Reset status and progress properties - ResetStatusAndProgressProperty(); + ResetStatusAndProgress(); // Clear the VO language list _sophonVOLanguageList?.Clear(); @@ -727,7 +728,7 @@ public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameSta // Set the progress bar to indetermined _status!.IsIncludePerFileIndicator = false; _status!.IsProgressPerFileIndetermined = false; - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; UpdateStatus(); // Initialize the info pair list @@ -760,9 +761,12 @@ public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameSta _gameVersionManager.GamePreset.SetVoiceLanguageID(setAsDefaultVO); // Get the remote total size and current total size - _progressTotalCount = sophonInfoPairList.Sum(x => x.ChunksInfo.FilesCount); - _progressTotalSize = sophonInfoPairList.Sum(x => x.ChunksInfo.TotalSize); - _progressTotalSizeCurrent = 0; + _progressAllCountTotal = sophonInfoPairList.Sum(x => x.ChunksInfo.FilesCount); + _progressAllSizeTotal = sophonInfoPairList.Sum(x => x.ChunksInfo.TotalSize); + _progressAllSizeCurrent = 0; + + // Set the display to Install Mode + _isSophonInUpdateMode = false; // Get the parallel options var parallelOptions = new ParallelOptions @@ -782,7 +786,7 @@ public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameSta // Set the progress bar to indetermined _status!.IsIncludePerFileIndicator = false; _status!.IsProgressPerFileIndetermined = false; - _status!.IsProgressTotalIndetermined = false; + _status!.IsProgressAllIndetermined = false; UpdateStatus(); // Declare the download delegate @@ -868,7 +872,7 @@ public virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameStat try { // Reset status and progress properties - ResetStatusAndProgressProperty(); + ResetStatusAndProgress(); // Clear the VO language list _sophonVOLanguageList?.Clear(); @@ -904,11 +908,15 @@ public virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameStat } } + // Get the remote chunk size + _progressPerFileSizeTotal = sophonUpdateAssetList.GetCalculatedDiffSize(true); + _progressPerFileSizeCurrent = 0; + // Get the remote total size and current total size - _progressTotalCount = sophonUpdateAssetList.Count(x => !x.IsDirectory); - _progressTotalSize = !isPreloadMode ? sophonUpdateAssetList.Sum(x => x.AssetSize) - : sophonUpdateAssetList.GetCalculatedDiffSize(false); - _progressTotalSizeCurrent = 0; + _progressAllCountTotal = sophonUpdateAssetList.Count(x => !x.IsDirectory); + _progressAllSizeTotal = !isPreloadMode ? sophonUpdateAssetList.Sum(x => x.AssetSize) + : _progressPerFileSizeTotal; + _progressAllSizeCurrent = 0; // Get the parallel options var parallelOptions = new ParallelOptions @@ -926,9 +934,13 @@ public virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameStat RestartStopwatch(); // Set the progress bar to indetermined - _status!.IsIncludePerFileIndicator = false; + _isSophonInUpdateMode = !isPreloadMode; + _status!.IsIncludePerFileIndicator = !isPreloadMode; _status!.IsProgressPerFileIndetermined = false; - _status!.IsProgressTotalIndetermined = false; + _status!.IsProgressAllIndetermined = false; + _status!.ActivityStatus = string.Format("{0}: {1}", + _isSophonInUpdateMode && !isPreloadMode ? Lang._Misc.UpdatingAndApplying : Lang._Misc.Downloading, + string.Format(Lang._Misc.PerFromTo, _progressAllCountCurrent, _progressAllCountTotal)); UpdateStatus(); // Get the update source and destination, also where the staging chunk files will be stored @@ -949,7 +961,7 @@ public virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameStat { // If preload mode, then only download the chunks await asset.DownloadDiffChunksAsync(httpClient, chunkPath, parallelChunksOptions, - UpdateSophonDownloadProgress, UpdateSophonDownloadStatus, + UpdateSophonFileTotalProgress, null, UpdateSophonDownloadStatus, isSophonPreloadCompleted); return; } @@ -960,7 +972,7 @@ await asset.DownloadDiffChunksAsync(httpClient, chunkPath, parallelChunksOptions // Otherwise, start the patching process await asset.WriteUpdateAsync(httpClient, gamePath, gamePath, chunkPath, - canDeleteChunks, parallelChunksOptions, UpdateSophonDownloadProgress, UpdateSophonDownloadStatus); + canDeleteChunks, parallelChunksOptions, UpdateSophonFileTotalProgress, UpdateSophonFileDownloadProgress, UpdateSophonDownloadStatus); }; // Enumerate in parallel and process the assets @@ -1088,7 +1100,8 @@ await asset.WriteToStreamAsync( () => new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite), parallelOptions, - UpdateSophonDownloadProgress, + UpdateSophonFileTotalProgress, + UpdateSophonFileDownloadProgress, UpdateSophonDownloadStatus ); } @@ -1102,8 +1115,8 @@ private async ValueTask RunPackageVerificationRoutine(GameInstallPackage as using (Stream fs = asset!.GetReadStream(_downloadThreadCount)!) { // Reset the per file size - _progressPerFileSize = fs.Length; - _status!.ActivityStatus = string.Format("{0}: {1}", Lang!._Misc!.Verifying, string.Format(Lang._Misc.PerFromTo!, _progressTotalCountCurrent, _progressTotalCount)); + _progressPerFileSizeTotal = fs.Length; + _status!.ActivityStatus = string.Format("{0}: {1}", Lang!._Misc!.Verifying, string.Format(Lang._Misc.PerFromTo!, _progressAllCountCurrent, _progressAllCountTotal)); UpdateStatus(); // Run the check and assign to hashLocal variable @@ -1116,7 +1129,7 @@ private async ValueTask RunPackageVerificationRoutine(GameInstallPackage as switch (await Dialog_GameInstallationFileCorrupt(_parentUI, asset.HashString, HexTool.BytesToHexUnsafe(hashLocal))) { case ContentDialogResult.Primary: - _progressTotalSizeCurrent -= asset.Size; + _progressAllSizeCurrent -= asset.Size; DeleteDownloadedFile(asset.PathOutput, _downloadThreadCount); return 0; case ContentDialogResult.None: @@ -1132,7 +1145,7 @@ private async ValueTask RunPackageVerificationRoutine(GameInstallPackage as } // Increment the total current count - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Return 1 as OK return 1; @@ -1186,7 +1199,7 @@ protected virtual async Task StartPackageInstallationInner(List 1; RestartStopwatch(); @@ -1211,9 +1224,9 @@ protected virtual async Task StartPackageInstallationInner(List entriesIndex, Li outputStream.Write(buffer, 0, read); // Increment total size - _progressTotalSizeCurrent += read; + _progressAllSizeCurrent += read; _progressPerFileSizeCurrent += read; if (!await CheckIfNeedRefreshStopwatch()) continue; // Assign local sizes to progress - _progress.ProgressPerFileDownload = _progressPerFileSizeCurrent; - _progress.ProgressPerFileSizeToDownload = _progressPerFileSize; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate the speed - _progress.ProgressTotalSpeed = _progressTotalSizeCurrent / _stopwatch.Elapsed.TotalSeconds; + _progress.ProgressAllSpeed = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; // Calculate percentage - _progress.ProgressTotalPercentage = Math.Round(((double)_progressTotalSizeCurrent / _progressTotalSize) * 100, 2); - _progress.ProgressPerFilePercentage = Math.Round(((double)_progressPerFileSizeCurrent / _progressPerFileSize) * 100, 2); + _progress.ProgressAllPercentage = Math.Round(((double)_progressAllSizeCurrent / _progressAllSizeTotal) * 100, 2); + _progress.ProgressPerFilePercentage = Math.Round(((double)_progressPerFileSizeCurrent / _progressPerFileSizeTotal) * 100, 2); // Calculate the timelapse - _progress.ProgressTotalTimeLeft = ((_progressTotalSize - _progressTotalSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progressAllSizeTotal - _progressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); UpdateAll(); } @@ -1814,18 +1827,18 @@ public virtual async ValueTask ApplyHdiffListPatch() { List hdiffEntry = TryGetHDiffList(); - _progress.ProgressTotalSizeToDownload = hdiffEntry.Sum(x => x.fileSize); - _progress.ProgressTotalDownload = 0; + _progress.ProgressAllSizeTotal = hdiffEntry.Sum(x => x.fileSize); + _progress.ProgressAllSizeCurrent = 0; _status.IsIncludePerFileIndicator = false; RestartStopwatch(); - _progressTotalCount = 1; - _progressTotalCountFound = hdiffEntry.Count; + _progressAllCountTotal = 1; + _progressAllCountFound = hdiffEntry.Count; HDiffPatch patcher = new HDiffPatch(); foreach (PkgVersionProperties entry in hdiffEntry) { - _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Patching, string.Format(Lang._Misc.PerFromTo, _progressTotalCount, _progressTotalCountFound)); + _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Patching, string.Format(Lang._Misc.PerFromTo, _progressAllCountTotal, _progressAllCountFound)); string patchBasePath = Path.Combine(_gamePath, ConverterTool.NormalizePath(entry.remoteName)); string sourceBasePath = GetBasePersistentDirectory(_gamePath, entry.remoteName); @@ -1863,11 +1876,11 @@ await Task.Run(() => { LogWriteLine($"Error while patching file: {entry.remoteName}. Skipping!\r\n{ex}", LogType.Warning, true); - _progress.ProgressTotalDownload += entry.fileSize; - _progress.ProgressTotalPercentage = Math.Round(((double)_progress.ProgressTotalDownload / _progress.ProgressTotalSizeToDownload) * 100, 2); - _progress.ProgressTotalSpeed = _progress.ProgressTotalDownload / _stopwatch.Elapsed.TotalSeconds; + _progress.ProgressAllSizeCurrent += entry.fileSize; + _progress.ProgressAllPercentage = Math.Round(((double)_progress.ProgressAllSizeCurrent / _progress.ProgressAllSizeTotal) * 100, 2); + _progress.ProgressAllSpeed = _progress.ProgressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; - _progress.ProgressTotalTimeLeft = ((_progress.ProgressTotalSizeToDownload - _progress.ProgressTotalDownload) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progress.ProgressAllSizeTotal - _progress.ProgressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); UpdateProgress(); } finally @@ -1883,7 +1896,7 @@ await Task.Run(() => LogWriteLine($"Failed while trying to delete temporary file: {destPath}, skipping!\r\n{ex}", LogType.Warning, true); } - _progressTotalCount++; + _progressAllCountTotal++; FileInfo patchFile = new FileInfo(patchPath); patchFile.IsReadOnly = false; @@ -1894,13 +1907,13 @@ await Task.Run(() => private async void EventListener_PatchEvent(object sender, PatchEvent e) { - _progress.ProgressTotalDownload += e.Read; + _progress.ProgressAllSizeCurrent += e.Read; if (await CheckIfNeedRefreshStopwatch()) { - _progress.ProgressTotalPercentage = Math.Round(((double)_progress.ProgressTotalDownload / _progress.ProgressTotalSizeToDownload) * 100, 2); - _progress.ProgressTotalSpeed = _progress.ProgressTotalDownload / _stopwatch.Elapsed.TotalSeconds; + _progress.ProgressAllPercentage = Math.Round(((double)_progress.ProgressAllSizeCurrent / _progress.ProgressAllSizeTotal) * 100, 2); + _progress.ProgressAllSpeed = _progress.ProgressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; - _progress.ProgressTotalTimeLeft = ((_progress.ProgressTotalSizeToDownload - _progress.ProgressTotalDownload) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progress.ProgressAllSizeTotal - _progress.ProgressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); UpdateProgress(); } } @@ -2810,8 +2823,8 @@ private async ValueTask InvokePackageDownloadRoutine(List pa int packageCount = packageList.Sum(x => x.Segments != null ? x.Segments.Count : 1); // Set progress count to beginning - _progressTotalCountCurrent = 1; - _progressTotalCount = packageCount; + _progressAllCountCurrent = 1; + _progressAllCountTotal = packageCount; RestartStopwatch(); // Subscribe the download progress to the event adapter @@ -2848,8 +2861,8 @@ private async ValueTask RunPackageDownloadRoutine(GameInstallPackage package, Ca { // Set the activity status _status.IsIncludePerFileIndicator = packageCount > 1; - _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Downloading, string.Format(Lang._Misc.PerFromTo, _progressTotalCountCurrent, _progressTotalCount)); - LogWriteLine($"Downloading package URL {_progressTotalCountCurrent}/{_progressTotalCount} ({ConverterTool.SummarizeSizeSimple(package.Size)}): {package.URL}"); + _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Downloading, string.Format(Lang._Misc.PerFromTo, _progressAllCountCurrent, _progressAllCountTotal)); + LogWriteLine($"Downloading package URL {_progressAllCountCurrent}/{_progressAllCountTotal} ({ConverterTool.SummarizeSizeSimple(package.Size)}): {package.URL}"); // Get the directory path string pathDir = Path.GetDirectoryName(package.PathOutput); @@ -2874,7 +2887,7 @@ private async ValueTask RunPackageDownloadRoutine(GameInstallPackage package, Ca await _httpClient.Download(package.URL, package.PathOutput, false, null, null, token); // Update status to merging - _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Merging, string.Format(Lang._Misc.PerFromTo, _progressTotalCountCurrent, _progressTotalCount)); + _status.ActivityStatus = string.Format("{0}: {1}", Lang._Misc.Merging, string.Format(Lang._Misc.PerFromTo, _progressAllCountCurrent, _progressAllCountTotal)); UpdateStatus(); _stopwatch.Stop(); @@ -2886,18 +2899,18 @@ private async ValueTask RunPackageDownloadRoutine(GameInstallPackage package, Ca } // Increment the total count - _progressTotalCountCurrent++; + _progressAllCountCurrent++; } private async ValueTask CheckExistingDownloadAsync(UIElement Content, List packageList) { - // If the _progressTotalSizeCurrent has the size, then + // If the _progressAllSizeCurrent has the size, then // display the reset or continue download dialog. // UPDATE: Ensure if the downloaded size is not the same as total. If no, then continue // showing the dialog - if (_progressTotalSizeCurrent > 0 && _progressTotalSize != _progressTotalSizeCurrent) + if (_progressAllSizeCurrent > 0 && _progressAllSizeTotal != _progressAllSizeCurrent) { - switch (await Dialog_ExistingDownload(Content, _progressTotalSizeCurrent, _progressTotalSize)) + switch (await Dialog_ExistingDownload(Content, _progressAllSizeCurrent, _progressAllSizeTotal)) { case ContentDialogResult.Primary: break; @@ -2913,8 +2926,8 @@ private async ValueTask CheckExistingDownloadAsync(UIElement Content, List packageList) { - // Reset the _progressTotalSizeCurrent to 0 - _progressTotalSizeCurrent = 0; + // Reset the _progressAllSizeCurrent to 0 + _progressAllSizeCurrent = 0; // Iterate and start deleting the existing file for (int i = 0; i < packageList.Count; i++) @@ -2979,7 +2992,7 @@ private long GetExistingDownloadPackageSize(List packageList return totalSize; } - private async ValueTask CheckDriveFreeSpace(UIElement Content, List packageList, long sizeDownloaded) + private async ValueTask CheckDriveFreeSpace(UIElement Content, List packageList, double sizeDownloaded) { // Get the information about the disk DriveInfo driveInfo = new DriveInfo(_gamePath); @@ -3096,7 +3109,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) _status.IsCompleted = true; _status.IsCanceled = false; // HACK: Fix the progress not achieving 100% while completed - _progress.ProgressTotalPercentage = 100f; + _progress.ProgressAllPercentage = 100f; _progress.ProgressPerFilePercentage = 100f; break; case CompletenessStatus.Cancelled: @@ -3147,17 +3160,17 @@ protected async Task TryGetSegmentedPackageRemoteSize(GameInstallPackage asset, protected async void DeltaPatchCheckProgress(object sender, PatchEvent e) { - _progress.ProgressTotalPercentage = e.ProgressPercentage; + _progress.ProgressAllPercentage = e.ProgressPercentage; - _progress.ProgressTotalTimeLeft = e.TimeLeft; - _progress.ProgressTotalSpeed = e.Speed; + _progress.ProgressAllTimeLeft = e.TimeLeft; + _progress.ProgressAllSpeed = e.Speed; - _progress.ProgressTotalSizeToDownload = e.TotalSizeToBePatched; - _progress.ProgressTotalDownload = e.CurrentSizePatched; + _progress.ProgressAllSizeTotal = e.TotalSizeToBePatched; + _progress.ProgressAllSizeCurrent = e.CurrentSizePatched; if (await CheckIfNeedRefreshStopwatch()) { - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; UpdateProgressBase(); UpdateStatus(); } @@ -3188,17 +3201,17 @@ protected void DeltaPatchCheckLogEvent(object sender, LoggerEvent e) protected async void DeltaPatchCheckProgress(object sender, TotalPerfileProgress e) { - _progress.ProgressTotalPercentage = e.ProgressTotalPercentage == 0 ? e.ProgressPerFilePercentage : e.ProgressTotalPercentage; + _progress.ProgressAllPercentage = e.ProgressAllPercentage == 0 ? e.ProgressPerFilePercentage : e.ProgressAllPercentage; - _progress.ProgressTotalTimeLeft = e.ProgressTotalTimeLeft; - _progress.ProgressTotalSpeed = e.ProgressTotalSpeed; + _progress.ProgressAllTimeLeft = e.ProgressAllTimeLeft; + _progress.ProgressAllSpeed = e.ProgressAllSpeed; - _progress.ProgressTotalSizeToDownload = e.ProgressTotalSizeToDownload; - _progress.ProgressTotalDownload = e.ProgressTotalDownload; + _progress.ProgressAllSizeTotal = e.ProgressAllSizeTotal; + _progress.ProgressAllSizeCurrent = e.ProgressAllSizeCurrent; if (await CheckIfNeedRefreshStopwatch()) { - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; UpdateProgressBase(); UpdateStatus(); } @@ -3210,26 +3223,26 @@ private async void ZipProgressAdapter(object sender, ExtractProgressProp e) { // Incrment current total size long lastSize = GetLastSize((long)e.TotalRead); - _progressTotalSizeCurrent += lastSize; + _progressAllSizeCurrent += lastSize; // Assign per file size _progressPerFileSizeCurrent = (long)e.TotalRead; - _progressPerFileSize = (long)e.TotalSize; + _progressPerFileSizeTotal = (long)e.TotalSize; // Assign local sizes to progress - _progress.ProgressPerFileDownload = _progressPerFileSizeCurrent; - _progress.ProgressPerFileSizeToDownload = _progressPerFileSize; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate the speed - _progress.ProgressTotalSpeed = _progressTotalSizeCurrent / _stopwatch.Elapsed.TotalSeconds; + _progress.ProgressAllSpeed = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; // Calculate percentage - _progress.ProgressTotalPercentage = Math.Round(((double)_progressTotalSizeCurrent / _progressTotalSize) * 100, 2); - _progress.ProgressPerFilePercentage = Math.Round(((double)_progressPerFileSizeCurrent / _progressPerFileSize) * 100, 2); + _progress.ProgressAllPercentage = Math.Round(((double)_progressAllSizeCurrent / _progressAllSizeTotal) * 100, 2); + _progress.ProgressPerFilePercentage = Math.Round(((double)_progressPerFileSizeCurrent / _progressPerFileSizeTotal) * 100, 2); // Calculate the timelapse - _progress.ProgressTotalTimeLeft = ((_progressTotalSize - _progressTotalSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progressAllSizeTotal - _progressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); UpdateAll(); } @@ -3249,37 +3262,35 @@ private async void HttpClientDownloadProgressAdapter(object sender, DownloadEven { // Set the progress bar not indetermined _status.IsProgressPerFileIndetermined = false; - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; if (e.State != DownloadState.Merging) { // Increment the total current size if status is not merging - _progressTotalSizeCurrent += e.Read; + _progressAllSizeCurrent += e.Read; // Increment the total last read - _progressTotalReadCurrent += e.Read; + _progressAllIOReadCurrent += e.Read; } if (await CheckIfNeedRefreshStopwatch()) { - _progress.DownloadEvent = e; - if (e.State != DownloadState.Merging) { // Assign local sizes to progress - _progress.ProgressPerFileDownload = _progressPerFileSizeCurrent; - _progress.ProgressPerFileSizeToDownload = _progressPerFileSize; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate the speed - _progress.ProgressTotalSpeed = _progressTotalReadCurrent / _stopwatch.Elapsed.TotalSeconds; + _progress.ProgressAllSpeed = _progressAllIOReadCurrent / _stopwatch.Elapsed.TotalSeconds; // Calculate percentage - _progress.ProgressPerFilePercentage = Math.Round(_progressPerFileSizeCurrent / (double)_progressPerFileSize * 100, 2); - _progress.ProgressTotalPercentage = Math.Round(_progressTotalSizeCurrent / (double)_progressTotalSize * 100, 2); + _progress.ProgressPerFilePercentage = Math.Round(_progressPerFileSizeCurrent / (double)_progressPerFileSizeTotal * 100, 2); + _progress.ProgressAllPercentage = Math.Round(_progressAllSizeCurrent / (double)_progressAllSizeTotal * 100, 2); // Calculate the timelapse - _progress.ProgressTotalTimeLeft = ((_progressTotalSize - _progressTotalSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progressAllSizeTotal - _progressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); } else { @@ -3289,18 +3300,18 @@ private async void HttpClientDownloadProgressAdapter(object sender, DownloadEven // If status is merging, then use progress for speed and timelapse from Http client // and set the rest from the base class - _progress.ProgressTotalTimeLeft = e.TimeLeft; - _progress.ProgressTotalSpeed = e.Speed; - _progress.ProgressPerFileDownload = _progressPerFileSizeCurrent; - _progress.ProgressPerFileSizeToDownload = _progressPerFileSize; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; - _progress.ProgressTotalPercentage = Math.Round(_progressTotalSizeCurrent / (double)_progressTotalSize * 100, 2); + _progress.ProgressAllTimeLeft = e.TimeLeft; + _progress.ProgressAllSpeed = e.Speed; + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; + _progress.ProgressAllPercentage = Math.Round(_progressAllSizeCurrent / (double)_progressAllSizeTotal * 100, 2); } // Update the status of per file size and current progress from Http client _progressPerFileSizeCurrent = e.SizeDownloaded; - _progressPerFileSize = e.SizeToBeDownloaded; + _progressPerFileSizeTotal = e.SizeToBeDownloaded; _progress.ProgressPerFilePercentage = e.ProgressPercentage; // Update the status @@ -3311,7 +3322,7 @@ private async void HttpClientDownloadProgressAdapter(object sender, DownloadEven protected override void RestartStopwatch() { // Reset read count to 0 - _progressTotalReadCurrent = 0; + _progressAllIOReadCurrent = 0; // Restart the stopwatch from base base.RestartStopwatch(); diff --git a/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs b/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs index 37250a03f..0cd25861a 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/GamePropertyBase.cs @@ -46,6 +46,7 @@ public GamePropertyBase(UIElement parentUI, IGameVersionCheck gameVersionManager protected CancellationTokenSourceWrapper _token { get; set; } protected Stopwatch _stopwatch { get; set; } protected Stopwatch _refreshStopwatch { get; set; } + protected Stopwatch _downloadSpeedRefreshStopwatch { get; set; } protected GameVersion _gameVersion { get => _isVersionOverride ? _gameVersionOverride : _gameVersionManager.GetGameExistingVersion().Value; } protected IGameVersionCheck _gameVersionManager { get; set; } protected IGameSettings _gameSettings { get; set; } diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index 79c5f2d51..552d62d6a 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -40,30 +40,41 @@ private void Init() { _status = new TotalPerfileStatus() { IsIncludePerFileIndicator = true }; _progress = new TotalPerfileProgress(); + _sophonStatus = new TotalPerfileStatus() { IsIncludePerFileIndicator = true }; + _sophonProgress = new TotalPerfileProgress(); _stopwatch = Stopwatch.StartNew(); _refreshStopwatch = Stopwatch.StartNew(); + _downloadSpeedRefreshStopwatch = Stopwatch.StartNew(); _assetIndex = new List(); } + private object _objLock = new object(); + public event EventHandler ProgressChanged; - public event EventHandler StatusChanged; - - protected TotalPerfileStatus _status; - protected TotalPerfileProgress _progress; - protected int _progressTotalCountCurrent; - protected int _progressTotalCountFound; - protected int _progressTotalCount; - protected long _progressTotalSizeCurrent; - protected long _progressTotalSizeFound; - protected long _progressTotalSize; - protected long _progressPerFileSizeCurrent; - protected long _progressPerFileSize; + public event EventHandler StatusChanged; + + protected TotalPerfileStatus _sophonStatus; + protected TotalPerfileProgress _sophonProgress; + protected TotalPerfileStatus _status; + protected TotalPerfileProgress _progress; + protected int _progressAllCountCurrent; + protected int _progressAllCountFound; + protected int _progressAllCountTotal; + protected long _progressAllSizeCurrent; + protected long _progressAllSizeFound; + protected long _progressAllSizeTotal; + protected double _progressAllIOReadCurrent; + protected long _progressPerFileSizeCurrent; + protected long _progressPerFileSizeTotal; + protected double _progressPerFileIOReadCurrent; // Extension for IGameInstallManager - protected double _progressTotalReadCurrent; + protected const int _downloadSpeedRefreshInterval = 5000; protected const int _refreshInterval = 100; + protected bool _isSophonInUpdateMode { get; set; } + #region ProgressEventHandlers - Fetch protected void _innerObject_ProgressAdapter(object sender, TotalPerfileProgress e) => ProgressChanged?.Invoke(sender, e); protected void _innerObject_StatusAdapter(object sender, TotalPerfileStatus e) => StatusChanged?.Invoke(sender, e); @@ -74,7 +85,7 @@ protected virtual void _httpClient_FetchAssetProgress(object sender, DownloadEve { // Update fetch status _status.IsProgressPerFileIndetermined = false; - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.ActivityPerFile = string.Format(Lang!._GameRepairPage!.PerProgressSubtitle3!, ConverterTool.SummarizeSizeSimple(e!.Speed)); } @@ -82,10 +93,10 @@ protected virtual void _httpClient_FetchAssetProgress(object sender, DownloadEve { // Update fetch progress _progress.ProgressPerFilePercentage = e.ProgressPercentage; - _progress.ProgressTotalDownload = e.SizeDownloaded; - _progress.ProgressTotalSizeToDownload = e.SizeToBeDownloaded; - _progress.ProgressTotalSpeed = e.Speed; - _progress.ProgressTotalTimeLeft = e.TimeLeft; + _progress.ProgressAllSizeCurrent = e.SizeDownloaded; + _progress.ProgressAllSizeTotal = e.SizeToBeDownloaded; + _progress.ProgressAllSpeed = e.Speed; + _progress.ProgressAllTimeLeft = e.TimeLeft; } // Push status and progress update @@ -100,25 +111,25 @@ protected virtual async void _httpClient_RepairAssetProgress(object sender, Down lock (_progress!) { _progress.ProgressPerFilePercentage = e!.ProgressPercentage; - _progress.ProgressPerFileDownload = e!.SizeDownloaded; - _progress.ProgressPerFileSizeToDownload = e!.SizeToBeDownloaded; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _progress.ProgressPerFileSizeCurrent = e!.SizeDownloaded; + _progress.ProgressPerFileSizeTotal = e!.SizeToBeDownloaded; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate speed - long speed = (long)(_progressTotalSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); - _progress.ProgressTotalSpeed = speed; - _progress.ProgressTotalTimeLeft = ((_progressTotalSizeCurrent - _progressTotalSize) / ConverterTool.Unzeroed(speed)) + long speed = (long)(_progressAllSizeCurrent / _stopwatch!.Elapsed.TotalSeconds); + _progress.ProgressAllSpeed = speed; + _progress.ProgressAllTimeLeft = ((_progressAllSizeCurrent - _progressAllSizeTotal) / ConverterTool.Unzeroed(speed)) .ToTimeSpanNormalized(); // Update current progress percentages - _progress.ProgressTotalPercentage = _progressTotalSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressTotalSizeCurrent, _progressTotalSize) : + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : 0; if (e.State != DownloadState.Merging) { - _progressTotalSizeCurrent += e.Read; + _progressAllSizeCurrent += e.Read; } } @@ -127,16 +138,16 @@ protected virtual async void _httpClient_RepairAssetProgress(object sender, Down lock (_status!) { // Update current activity status - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.IsProgressPerFileIndetermined = false; // Set time estimation string - string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress!.ProgressTotalTimeLeft); + string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress!.ProgressAllTimeLeft); - _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressTotalSpeed)); - _status.ActivityTotal = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, - ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), - ConverterTool.SummarizeSizeSimple(_progressTotalSize)) + $" | {timeLeftString}"; + _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(); @@ -144,13 +155,13 @@ protected virtual async void _httpClient_RepairAssetProgress(object sender, Down } } - protected virtual void UpdateRepairStatus(string activityStatus, string activityTotal, bool isPerFileIndetermined) + protected virtual void UpdateRepairStatus(string activityStatus, string ActivityAll, bool isPerFileIndetermined) { lock (_status!) { // Set repair activity status _status.ActivityStatus = activityStatus; - _status.ActivityTotal = activityTotal; + _status.ActivityAll = ActivityAll; _status.IsProgressPerFileIndetermined = isPerFileIndetermined; } @@ -165,11 +176,11 @@ protected virtual async void RepairTypeActionPatching_ProgressChanged(object sen lock (_progress!) { _progress.ProgressPerFilePercentage = e!.ProgressPercentage; - _progress.ProgressTotalSpeed = e!.Speed; + _progress.ProgressAllSpeed = e!.Speed; // Update current progress percentages - _progress.ProgressTotalPercentage = _progressTotalSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressTotalSizeCurrent, _progressTotalSize) : + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : 0; } @@ -178,10 +189,10 @@ protected virtual async void RepairTypeActionPatching_ProgressChanged(object sen lock (_status!) { // Update current activity status - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.IsProgressPerFileIndetermined = false; - _status.ActivityPerFile = string.Format(Lang!._GameRepairPage!.PerProgressSubtitle5!, ConverterTool.SummarizeSizeSimple(_progress!.ProgressTotalSpeed)); - _status.ActivityTotal = string.Format(Lang._GameRepairPage.PerProgressSubtitle2!, ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressTotalSize)); + _status.ActivityPerFile = string.Format(Lang!._GameRepairPage!.PerProgressSubtitle5!, ConverterTool.SummarizeSizeSimple(_progress!.ProgressAllSpeed)); + _status.ActivityAll = string.Format(Lang._GameRepairPage.PerProgressSubtitle2!, ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)); } // Trigger update @@ -199,35 +210,35 @@ protected virtual async void UpdateProgressCRC() { // Update current progress percentages _progress.ProgressPerFilePercentage = _progressPerFileSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressPerFileSizeCurrent, _progressPerFileSize) : + ConverterTool.GetPercentageNumber(_progressPerFileSizeCurrent, _progressPerFileSizeTotal) : 0; - _progress.ProgressTotalPercentage = _progressTotalSizeCurrent != 0 ? - ConverterTool.GetPercentageNumber(_progressTotalSizeCurrent, _progressTotalSize) : + _progress.ProgressAllPercentage = _progressAllSizeCurrent != 0 ? + ConverterTool.GetPercentageNumber(_progressAllSizeCurrent, _progressAllSizeTotal) : 0; // Update the progress of total size - _progress.ProgressPerFileDownload = _progressPerFileSizeCurrent; - _progress.ProgressPerFileSizeToDownload = _progressPerFileSize; - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _progress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _progress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; + _progress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _progress.ProgressAllSizeTotal = _progressAllSizeTotal; // Calculate current speed and update the status and progress speed - _progress.ProgressTotalSpeed = _progressTotalSizeCurrent / _stopwatch!.Elapsed.TotalSeconds; + _progress.ProgressAllSpeed = _progressAllSizeCurrent / _stopwatch!.Elapsed.TotalSeconds; // Calculate the timelapse - _progress.ProgressTotalTimeLeft = ((_progressTotalSize - _progressTotalSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((_progressAllSizeTotal - _progressAllSizeCurrent) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); } lock (_status!) { // Set time estimation string - string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress.ProgressTotalTimeLeft); + string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress.ProgressAllTimeLeft); // Update current activity status - _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressTotalSpeed)); - _status.ActivityTotal = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, - ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), - ConverterTool.SummarizeSizeSimple(_progressTotalSize)) + $" | {timeLeftString}"; + _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 @@ -247,24 +258,24 @@ protected virtual async void UpdateProgressCopyStream(long currentPosition, int _progress.ProgressPerFilePercentage = ConverterTool.GetPercentageNumber(currentPosition, totalReadSize); // Update the progress of total size - _progress.ProgressPerFileDownload = currentPosition; - _progress.ProgressPerFileSizeToDownload = totalReadSize; + _progress.ProgressPerFileSizeCurrent = currentPosition; + _progress.ProgressPerFileSizeTotal = totalReadSize; // Calculate current speed and update the status and progress speed - _progress.ProgressTotalSpeed = currentPosition / _stopwatch!.Elapsed.TotalSeconds; + _progress.ProgressAllSpeed = currentPosition / _stopwatch!.Elapsed.TotalSeconds; // Calculate the timelapse - _progress.ProgressTotalTimeLeft = ((totalReadSize - currentPosition) / ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + _progress.ProgressAllTimeLeft = ((totalReadSize - currentPosition) / ConverterTool.Unzeroed(_progress.ProgressAllSpeed)).ToTimeSpanNormalized(); } lock (_status!) { // Set time estimation string - string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress.ProgressTotalTimeLeft); + string timeLeftString = string.Format(Lang!._Misc!.TimeRemainHMSFormat!, _progress.ProgressAllTimeLeft); // Update current activity status - _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressTotalSpeed)); - _status.ActivityTotal = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, + _status.ActivityPerFile = string.Format(Lang._Misc.Speed!, ConverterTool.SummarizeSizeSimple(_progress.ProgressAllSpeed)); + _status.ActivityAll = string.Format(Lang._GameRepairPage!.PerProgressSubtitle2!, ConverterTool.SummarizeSizeSimple(currentPosition), ConverterTool.SummarizeSizeSimple(totalReadSize)) + $" | {timeLeftString}"; } @@ -276,38 +287,70 @@ protected virtual async void UpdateProgressCopyStream(long currentPosition, int #endregion #region ProgressEventHandlers - SophonInstaller - protected async void UpdateSophonDownloadProgress(long read) + protected async void UpdateSophonFileTotalProgress(long read) { - Interlocked.Add(ref _progressTotalSizeCurrent, read); - _progressTotalReadCurrent += read; + Interlocked.Add(ref _progressAllSizeCurrent, read); + _progressAllIOReadCurrent += read; - if (await CheckIfNeedRefreshStopwatch()) + if (_refreshStopwatch!.ElapsedMilliseconds > _refreshInterval) { // Assign local sizes to progress - _progress.ProgressTotalDownload = _progressTotalSizeCurrent; - _progress.ProgressTotalSizeToDownload = _progressTotalSize; + _sophonProgress.ProgressAllSizeCurrent = _progressAllSizeCurrent; + _sophonProgress.ProgressAllSizeTotal = _progressAllSizeTotal; + _sophonProgress.ProgressPerFileSizeCurrent = _progressPerFileSizeCurrent; + _sophonProgress.ProgressPerFileSizeTotal = _progressPerFileSizeTotal; // Calculate the speed - _progress.ProgressTotalSpeed = _progressTotalSizeCurrent / _stopwatch.Elapsed.TotalSeconds; + double speedAll = _progressAllIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speedPerFile = _progressPerFileIOReadCurrent / _downloadSpeedRefreshStopwatch.Elapsed.TotalSeconds; + double speedAllNoReset = _progressAllSizeCurrent / _stopwatch.Elapsed.TotalSeconds; + _sophonProgress.ProgressAllSpeed = speedAll; + _sophonProgress.ProgressPerFileSpeed = speedPerFile; + + // Calculate Count + _sophonProgress.ProgressAllEntryCountCurrent = _progressAllCountCurrent; + _sophonProgress.ProgressAllEntryCountTotal = _progressAllCountTotal; // Calculate percentage - _progress.ProgressTotalPercentage = - Math.Round((double)_progressTotalSizeCurrent / _progressTotalSize * 100, 2); + _sophonProgress.ProgressAllPercentage = + Math.Round((double)_progressAllSizeCurrent / _progressAllSizeTotal * 100, 2); + _sophonProgress.ProgressPerFilePercentage = + Math.Round((double)_progressPerFileSizeCurrent / _progressPerFileSizeTotal * 100, 2); + // Calculate the timelapse - _progress.ProgressTotalTimeLeft = - ((_progressTotalSize - _progressTotalSizeCurrent) / - ConverterTool.Unzeroed(_progress.ProgressTotalSpeed)).ToTimeSpanNormalized(); + double progressTimeAvg = (_progressAllSizeTotal - _progressAllSizeCurrent) / speedAllNoReset; + _sophonProgress.ProgressAllTimeLeft = progressTimeAvg.ToTimeSpanNormalized(); + + // Update progress + ProgressChanged?.Invoke(this, _sophonProgress); - UpdateProgress(); + if (_downloadSpeedRefreshInterval < _downloadSpeedRefreshStopwatch!.ElapsedMilliseconds) + { + _progressAllIOReadCurrent = 0; + _progressPerFileIOReadCurrent = 0; + _downloadSpeedRefreshStopwatch.Restart(); + } + + _refreshStopwatch.Restart(); + await Task.Delay(_refreshInterval); + } + } + + protected void UpdateSophonFileDownloadProgress(long downloadedWrite, long currentWrite) + { + Interlocked.Add(ref _progressPerFileSizeCurrent, downloadedWrite); + lock (_objLock) + { + _progressPerFileIOReadCurrent += currentWrite; } } protected void UpdateSophonDownloadStatus(SophonAsset asset) { - Interlocked.Add(ref _progressTotalCountCurrent, 1); - _status.ActivityStatus = string.Format("{0}: {1}", Lang!._Misc!.Downloading, - string.Format(Lang._Misc.PerFromTo!, _progressTotalCountCurrent, - _progressTotalCount)); + Interlocked.Add(ref _progressAllCountCurrent, 1); + _status.ActivityStatus = string.Format("{0}: {1}", + _isSophonInUpdateMode ? Lang._Misc.Updating : Lang._Misc.Downloading, + string.Format(Lang._Misc.PerFromTo, _progressAllCountCurrent, _progressAllCountTotal)); UpdateStatus(); } @@ -337,10 +380,10 @@ protected async Task DoCopyStreamProgress(Stream source, Stream target, Cancella RestartStopwatch(); bool isLastPerfileStateIndetermined = _status!.IsProgressPerFileIndetermined; - bool isLastTotalStateIndetermined = _status!.IsProgressTotalIndetermined; + bool isLastTotalStateIndetermined = _status!.IsProgressAllIndetermined; _status.IsProgressPerFileIndetermined = false; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; byte[] buffer = ArrayPool.Shared.Rent(16 << 10); try @@ -356,7 +399,7 @@ protected async Task DoCopyStreamProgress(Stream source, Stream target, Cancella finally { _status!.IsProgressPerFileIndetermined = isLastPerfileStateIndetermined; - _status!.IsProgressTotalIndetermined = isLastTotalStateIndetermined; + _status!.IsProgressAllIndetermined = isLastTotalStateIndetermined; ArrayPool.Shared.Return(buffer); } } @@ -500,9 +543,9 @@ protected void ResetStatusAndProgressProperty() _status!.IsAssetEntryPanelShow = false; // Reset all total activity status - _status.ActivityStatus = Lang!._GameRepairPage!.StatusNone; - _status.ActivityTotal = Lang._GameRepairPage.StatusNone; - _status.IsProgressTotalIndetermined = false; + _status.ActivityStatus = Lang!._GameRepairPage!.StatusNone; + _status.ActivityAll = Lang._GameRepairPage.StatusNone; + _status.IsProgressAllIndetermined = false; // Reset all per-file activity status _status.ActivityPerFile = Lang._GameRepairPage.StatusNone; @@ -514,18 +557,23 @@ protected void ResetStatusAndProgressProperty() _status.IsCanceled = false; // Reset all total activity progress - _progress!.ProgressPerFilePercentage = 0; - _progress.ProgressTotalPercentage = 0; - _progress.ProgressTotalEntryCount = 0; - _progress.ProgressTotalSpeed = 0; + _progress!.ProgressPerFilePercentage = 0; + _progress.ProgressAllPercentage = 0; + _progress.ProgressPerFileSpeed = 0; + _progress.ProgressAllSpeed = 0; + + _progress.ProgressAllEntryCountCurrent = 0; + _progress.ProgressAllEntryCountTotal = 0; + _progress.ProgressPerFileEntryCountCurrent = 0; + _progress.ProgressPerFileEntryCountTotal = 0; // Reset all inner counter - _progressTotalCountCurrent = 0; - _progressTotalCount = 0; - _progressTotalSizeCurrent = 0; - _progressTotalSize = 0; + _progressAllCountCurrent = 0; + _progressAllCountTotal = 0; + _progressAllSizeCurrent = 0; + _progressAllSizeTotal = 0; _progressPerFileSizeCurrent = 0; - _progressPerFileSize = 0; + _progressPerFileSizeTotal = 0; } } @@ -720,12 +768,12 @@ protected async Task TryRunExamineThrow(Task action) protected void SetFoundToTotalValue() { // Assign found count and size to total count and size - _progressTotalCount = _progressTotalCountFound; - _progressTotalSize = _progressTotalSizeFound; + _progressAllCountTotal = _progressAllCountFound; + _progressAllSizeTotal = _progressAllSizeFound; // Reset found count and size - _progressTotalCountFound = 0; - _progressTotalSizeFound = 0; + _progressAllCountFound = 0; + _progressAllSizeFound = 0; } protected bool SummarizeStatusAndProgress(List assetIndex, string msgIfFound, string msgIfClear) @@ -821,7 +869,7 @@ protected virtual async ValueTask CheckHashAsync(Stream stream, HashAlgo lock (this) { // Increment total size counter - if (updateTotalProgress) _progressTotalSizeCurrent += read; + if (updateTotalProgress) _progressAllSizeCurrent += read; // Increment per file size counter _progressPerFileSizeCurrent += read; } @@ -866,7 +914,7 @@ protected virtual async ValueTask RunPatchTask(Http _httpClient, CancellationTok if (!IsArrayMatch(patchCRC, patchHash.Span)) { // Revert back the total size - _progressTotalSizeCurrent -= patchSize; + _progressAllSizeCurrent -= patchSize; // Redownload the patch file await RunDownloadTask(patchSize, patchOutputFile, patchURL, _httpClient, token)!; @@ -1021,28 +1069,26 @@ protected virtual void PopRepairAssetEntry() protected async Task CheckIfNeedRefreshStopwatch() { - if (_refreshStopwatch!.ElapsedMilliseconds > _refreshInterval) + lock (_objLock) { - _refreshStopwatch.Restart(); - return true; + if (_refreshStopwatch!.ElapsedMilliseconds > _refreshInterval) + { + _refreshStopwatch.Restart(); + return true; + } } await Task.Delay(_refreshInterval); return false; } + protected void UpdateAll() { UpdateStatus(); UpdateProgress(); } - protected virtual void UpdateProgress() - { - _progress!.ProgressPerFilePercentage = _progress.ProgressPerFilePercentage.UnNaNInfinity(); - _progress.ProgressTotalPercentage = _progress.ProgressTotalPercentage.UnNaNInfinity(); - ProgressChanged?.Invoke(this, _progress); - } - + protected virtual void UpdateProgress() => ProgressChanged?.Invoke(this, _progress); protected virtual void UpdateStatus() => StatusChanged?.Invoke(this, _status); protected virtual void RestartStopwatch() => _stopwatch!.Restart(); #endregion diff --git a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs index 70eecb7f9..418654861 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs @@ -1,7 +1,6 @@ using CollapseLauncher.Helper.Metadata; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Http; using System; using System.IO; // ReSharper disable CheckNamespace @@ -10,42 +9,50 @@ namespace CollapseLauncher { internal class TotalPerfileProgress { - private double _progressPerFilePercentage; - private double _progressTotalPercentage; - private double _progressTotalEntryCount; - private double _progressTotalSpeed; + private double _progressPerFilePercentage; + private double _progressPerFileSpeed; + private double _progressAllPercentage; + private double _progressAllSpeed; + private long _progressPerFileEntryCountCurrent; + private long _progressPerFileEntryCountTotal; + private long _progressAllEntryCountCurrent; + private long _progressAllEntryCountTotal; public double ProgressPerFilePercentage { get => _progressPerFilePercentage; set => _progressPerFilePercentage = value.UnNaNInfinity(); } - public double ProgressTotalPercentage { get => _progressTotalPercentage; set => _progressTotalPercentage = value.UnNaNInfinity(); } - public double ProgressTotalEntryCount { get => _progressTotalEntryCount; set => _progressTotalEntryCount = value.UnNaNInfinity(); } - public double ProgressTotalSpeed { get => _progressTotalSpeed; set => _progressTotalSpeed = value.UnNaNInfinity(); } + public double ProgressPerFileSpeed { get => _progressPerFileSpeed; set => _progressPerFileSpeed = value.UnNaNInfinity(); } + public double ProgressAllPercentage { get => _progressAllPercentage; set => _progressAllPercentage = value.UnNaNInfinity(); } + public double ProgressAllSpeed { get => _progressAllSpeed; set => _progressAllSpeed = value.UnNaNInfinity(); } + + public long ProgressPerFileEntryCountCurrent { get => _progressPerFileEntryCountCurrent; set => _progressPerFileEntryCountCurrent = value; } + public long ProgressPerFileEntryCountTotal { get => _progressPerFileEntryCountTotal; set => _progressPerFileEntryCountTotal = value; } + public long ProgressAllEntryCountCurrent { get => _progressAllEntryCountCurrent; set => _progressAllEntryCountCurrent = value; } + public long ProgressAllEntryCountTotal { get => _progressAllEntryCountTotal; set => _progressAllEntryCountTotal = value; } // Extension for IGameInstallManager - public long ProgressPerFileDownload { get; set; } - public long ProgressPerFileSizeToDownload { get; set; } - public long ProgressTotalDownload { get; set; } - public long ProgressTotalSizeToDownload { get; set; } - public TimeSpan ProgressTotalTimeLeft; - public DownloadEvent DownloadEvent; + public double ProgressPerFileSizeCurrent { get; set; } + public double ProgressPerFileSizeTotal { get; set; } + public double ProgressAllSizeCurrent { get; set; } + public double ProgressAllSizeTotal { get; set; } + public TimeSpan ProgressAllTimeLeft { get; set; } } internal class TotalPerfileStatus { - public string ActivityStatus; + public string ActivityStatus { get; set; } - public string ActivityTotal; - public bool IsProgressTotalIndetermined; + public string ActivityAll { get; set; } + public bool IsProgressAllIndetermined { get; set; } - public string ActivityPerFile; - public bool IsProgressPerFileIndetermined; + public string ActivityPerFile { get; set; } + public bool IsProgressPerFileIndetermined { get; set; } - public bool IsAssetEntryPanelShow; + public bool IsAssetEntryPanelShow { get; set; } - public bool IsCompleted; - public bool IsCanceled; - public bool IsRunning; + public bool IsCompleted { get; set; } + public bool IsCanceled { get; set; } + public bool IsRunning { get; set; } - public bool IsIncludePerFileIndicator; + public bool IsIncludePerFileIndicator { get; set; } } internal struct GameVendorProp diff --git a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs index 61385396c..f1f1d531f 100644 --- a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs +++ b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs @@ -28,5 +28,6 @@ internal interface IGameInstallManager : IBackgroundActivity, IDisposable bool StartAfterInstall { get; set; } bool IsUseSophon { get; } + bool IsSophonInUpdateMode { get; } } } diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs index 91da32799..ecb4c7ee2 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs @@ -21,7 +21,7 @@ private async Task Check(List assetIndex, CancellationToke List brokenAssetIndex = new List(); // Set Indetermined status as false - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.IsProgressPerFileIndetermined = false; // Show the asset entry panel @@ -142,10 +142,10 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List AssetEntry.Add( new AssetProperty( @@ -191,14 +191,14 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List AssetEntry.Add( new AssetProperty( @@ -239,14 +239,14 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List AssetEntry.Add( new AssetProperty( @@ -291,7 +291,7 @@ private void CheckRedundantFiles(List targetAssetIndex) if (fInfo.Exists) { // Update total found progress - _progressTotalCountFound++; + _progressAllCountFound++; // Get the stripped relative name string strippedName = fInfo.FullName.AsSpan().Slice(_gamePath.Length + 1).ToString(); @@ -332,7 +332,7 @@ private void CheckRedundantFiles(List targetAssetIndex) fInfo = new FileInfo(_Entry); // Update total found progress - _progressTotalCountFound++; + _progressAllCountFound++; // Get the stripped relative name string strippedName = fInfo.FullName.AsSpan().Slice(_gamePath.Length + 1).ToString(); diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index 0ae4a16bf..c10a8bae1 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -23,7 +23,7 @@ private async ValueTask> Fetch(List TryDecryptAndParseDispatcher(YSDispatchInfo di #region Tools private void CountAssetIndex(List assetIndex) { - _progressTotalSize = assetIndex.Sum(x => x.fileSize); - _progressTotalCount = assetIndex.Count; + _progressAllSizeTotal = assetIndex.Sum(x => x.fileSize); + _progressAllCountTotal = assetIndex.Count; } private void EnumerateManifestToAssetIndex(string path, string filter, List assetIndex, Dictionary hashtable, string parentPath = "", string acceptedExtension = "", string parentURL = "", bool forceStoreInStreaming = false) diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/GenshinRepair.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/GenshinRepair.cs index 22de03b2b..13845a3fe 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/GenshinRepair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/GenshinRepair.cs @@ -83,7 +83,7 @@ private async Task CheckRoutine() // either way, returns false. return SummarizeStatusAndProgress( _assetIndex, - string.Format(Lang._GameRepairPage.Status3, _progressTotalCountFound, SummarizeSizeSimple(_progressTotalSizeFound)), + string.Format(Lang._GameRepairPage.Status3, _progressAllCountFound, SummarizeSizeSimple(_progressAllSizeFound)), Lang._GameRepairPage.Status4); } diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs index 67bc0db93..b9d6258b4 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Repair.cs @@ -17,7 +17,7 @@ private async Task Repair(List repairAssetIndex, Can { // Set total activity string as "Waiting for repair process to start..." _status.ActivityStatus = Lang._GameRepairPage.Status11; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; _status.IsProgressPerFileIndetermined = true; // Update status @@ -63,11 +63,11 @@ private async Task Repair(List repairAssetIndex, Can private async Task RepairAssetTypeGeneric(PkgVersionProperties asset, Http _httpClient, CancellationToken token) { // Increment total count current - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Set repair activity status UpdateRepairStatus( string.Format(Lang._GameRepairPage.Status8, asset.remoteName), - string.Format(Lang._GameRepairPage.PerProgressSubtitle2, _progressTotalCountCurrent, _progressTotalCount), + string.Format(Lang._GameRepairPage.PerProgressSubtitle2, _progressAllCountCurrent, _progressAllCountTotal), true); // If file is unused, then delete diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs index 29270335c..38f64108d 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Check.cs @@ -21,7 +21,7 @@ private async Task Check(List assetIndex, CancellationToke List brokenAssetIndex = new List(); // Set Indetermined status as false - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.IsProgressPerFileIndetermined = false; // Show the asset entry panel @@ -84,10 +84,10 @@ private async Task Check(List assetIndex, CancellationToke private void CheckAssetTypeVideo(FilePropertiesRemote asset, List targetAssetIndex) { // Increment current total count - // _progressTotalCountCurrent++; + // _progressAllCountCurrent++; // Increment current Total Size - // _progressTotalSizeCurrent += asset.S; + // _progressAllSizeCurrent += asset.S; // Get file path string filePath = Path.Combine(_gamePath, ConverterTool.NormalizePath(asset.N)); @@ -97,8 +97,8 @@ private void CheckAssetTypeVideo(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -126,10 +126,10 @@ private async ValueTask CheckAssetTypeAudio(FilePropertiesRemote asset, List AssetEntry.Add( @@ -239,10 +239,10 @@ private async ValueTask CheckAssetTypeGeneric(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -387,10 +387,10 @@ private async ValueTask CheckAssetTypeBlocks(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -456,14 +456,14 @@ private async ValueTask CheckAssetTypeBlocks(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -506,8 +506,8 @@ private async ValueTask CheckAssetTypeBlocks(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -665,7 +665,7 @@ private void GetUnusedAssetIndexList(List catalog, List assetIndex, CancellationToke { // Set total activity string as "Loading Indexes..." _status!.ActivityStatus = Lang!._GameRepairPage!.Status2!; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; UpdateStatus(); // Initialize the Senadina File Identifier @@ -366,7 +366,7 @@ private async ValueTask IsCGFileAvailable(CGMetadata cgInfo, string baseUR // Update the status _status!.ActivityStatus = string.Format(Lang._GameRepairPage.Status14, cgInfo.CgExtraKey); - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.IsProgressPerFileIndetermined = true; UpdateStatus(); @@ -493,7 +493,7 @@ private async ValueTask IsAudioFileAvailable(ManifestAssetInfo audioInfo, // Update the status // TODO: Localize _status!.ActivityStatus = string.Format(Lang._GameRepairPage.Status15, audioInfo.Path); - _status!.IsProgressTotalIndetermined = true; + _status!.IsProgressAllIndetermined = true; _status!.IsProgressPerFileIndetermined = true; UpdateStatus(); @@ -760,11 +760,11 @@ private void CountAssetIndex(List assetIndex) // Filter out video assets List assetIndexFiltered = assetIndex!.Where(x => x!.FT != FileType.Video).ToList(); - // Sum the assetIndex size and assign to _progressTotalSize - _progressTotalSize = assetIndexFiltered.Sum(x => x!.S); + // Sum the assetIndex size and assign to _progressAllSize + _progressAllSizeTotal = assetIndexFiltered.Sum(x => x!.S); - // Assign the assetIndex count to _progressTotalCount - _progressTotalCount = assetIndexFiltered.Count; + // Assign the assetIndex count to _progressAllCount + _progressAllCountTotal = assetIndexFiltered.Count; } #endregion } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs index c50cbf821..f597093be 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/HonkaiRepair.cs @@ -107,7 +107,7 @@ private async Task CheckRoutine() // either way, returns false. return SummarizeStatusAndProgress( _assetIndex, - string.Format(Lang._GameRepairPage.Status3, _progressTotalCountFound, ConverterTool.SummarizeSizeSimple(_progressTotalSizeFound)), + string.Format(Lang._GameRepairPage.Status3, _progressAllCountFound, ConverterTool.SummarizeSizeSimple(_progressAllSizeFound)), Lang._GameRepairPage.Status4); } diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs index b55f1de01..261587975 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Repair.cs @@ -19,7 +19,7 @@ private async Task Repair(List repairAssetIndex, Can { // Set total activity string as "Waiting for repair process to start..." _status.ActivityStatus = Lang._GameRepairPage.Status11; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; _status.IsProgressPerFileIndetermined = true; // Update status @@ -92,7 +92,7 @@ private async Task RepairOrPatchTypeAudio(FilePropertiesRemote asset, Http _http private async Task RepairTypeAudioActionPatching(FilePropertiesRemote asset, Http _httpClient, CancellationToken token) { // Increment total count current - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Declare variables for patch file and URL and new file path string patchURL = ConverterTool.CombineURLFromString(string.Format(_audioPatchBaseRemotePath, $"{_gameVersion.Major}_{_gameVersion.Minor}", _gameServer.Manifest.ManifestAudio.ManifestAudioRevision), asset.AudioPatchInfo.Value.PatchFilename); @@ -103,7 +103,7 @@ private async Task RepairTypeAudioActionPatching(FilePropertiesRemote asset, Htt // Set downloading patch status UpdateRepairStatus( string.Format(Lang._GameRepairPage.Status12, asset.N), - string.Format(Lang._GameRepairPage.PerProgressSubtitle4, ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressTotalSize)), + string.Format(Lang._GameRepairPage.PerProgressSubtitle4, ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)), true); // Run patching task @@ -121,11 +121,11 @@ await RunPatchTask(_httpClient, token, asset.AudioPatchInfo.Value.PatchFileSize, private async Task RepairAssetTypeGeneric(FilePropertiesRemote asset, Http _httpClient, CancellationToken token, string customURL = null) { // Increment total count current - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Set repair activity status UpdateRepairStatus( string.Format(asset.FT == FileType.Blocks ? Lang._GameRepairPage.Status9 : Lang._GameRepairPage.Status8, asset.FT == FileType.Blocks ? asset.CRC : asset.N), - string.Format(Lang._GameRepairPage.PerProgressSubtitle2, ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressTotalSize)), + string.Format(Lang._GameRepairPage.PerProgressSubtitle2, ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)), true); // Set URL of the asset @@ -175,7 +175,7 @@ private async Task RepairAssetTypeBlocks(FilePropertiesRemote asset, Http _httpC if (asset.IsPatchApplicable) { // Increment total count current and update the status - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Do patching await RepairTypeBlocksActionPatching(asset, _httpClient, token); @@ -199,7 +199,7 @@ private async Task RepairTypeBlocksActionPatching(FilePropertiesRemote asset, Ht // Set downloading patch status UpdateRepairStatus( string.Format(Lang._GameRepairPage.Status13, asset.CRC), - string.Format(Lang._GameRepairPage.PerProgressSubtitle4, ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressTotalSize)), + string.Format(Lang._GameRepairPage.PerProgressSubtitle4, ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)), true); // Run patching task diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Check.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Check.cs index bf99a63cd..05d0fe00e 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Check.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Check.cs @@ -68,7 +68,7 @@ private async Task Check(List assetIndex, CancellationToke List brokenAssetIndex = new List(); // Set Indetermined status as false - _status.IsProgressTotalIndetermined = false; + _status.IsProgressAllIndetermined = false; _status.IsProgressPerFileIndetermined = false; // Show the asset entry panel @@ -122,10 +122,10 @@ private async ValueTask CheckGenericAssetType(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -177,8 +177,8 @@ private async ValueTask CheckGenericAssetType(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -206,10 +206,10 @@ private async ValueTask CheckAssetType(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -260,14 +260,14 @@ private async ValueTask CheckAssetType(FilePropertiesRemote asset, List AssetEntry.Add( new AssetProperty( @@ -328,8 +328,8 @@ async ValueTask CheckFile(string fileNameToOpen, FilePropertiesRemote asset, Lis // If local and asset CRC doesn't match, then add the asset if (!IsArrayMatch(localCRC, asset.CRCArray)) { - _progressTotalSizeFound += asset.S; - _progressTotalCountFound++; + _progressAllSizeFound += asset.S; + _progressAllCountFound++; Dispatch(() => AssetEntry.Add( new AssetProperty( diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index 4d0895392..b12f82b17 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -63,7 +63,7 @@ private async Task Fetch(List assetIndex, CancellationToke { // Set total activity string as "Loading Indexes..." _status.ActivityStatus = Lang._GameRepairPage.Status2; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; UpdateStatus(); StarRailRepairExtension.ClearHashtable(); @@ -452,11 +452,11 @@ private bool FilterCurrentAudioLangFile(SRAsset asset, string[] langNames, out b private void CountAssetIndex(List assetIndex) { - // Sum the assetIndex size and assign to _progressTotalSize - _progressTotalSize = assetIndex.Sum(x => x.S); + // Sum the assetIndex size and assign to _progressAllSize + _progressAllSizeTotal = assetIndex.Sum(x => x.S); - // Assign the assetIndex count to _progressTotalCount - _progressTotalCount = assetIndex.Count; + // Assign the assetIndex count to _progressAllCount + _progressAllCountTotal = assetIndex.Count; } private FileType ConvertFileTypeEnum(SRAssetType assetType) => assetType switch diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs index 0df5fdadf..0c34ec1c8 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Repair.cs @@ -18,7 +18,7 @@ private async Task Repair(List repairAssetIndex, Can { // Set total activity string as "Waiting for repair process to start..." _status.ActivityStatus = Lang._GameRepairPage.Status11; - _status.IsProgressTotalIndetermined = true; + _status.IsProgressAllIndetermined = true; _status.IsProgressPerFileIndetermined = true; // Update status @@ -74,11 +74,11 @@ private async Task Repair(List repairAssetIndex, Can private async Task RepairAssetTypeGeneric(FilePropertiesRemote asset, Http _httpClient, CancellationToken token) { // Increment total count current - _progressTotalCountCurrent++; + _progressAllCountCurrent++; // Set repair activity status UpdateRepairStatus( string.Format(Lang._GameRepairPage.Status8, Path.GetFileName(asset.N)), - string.Format(Lang._GameRepairPage.PerProgressSubtitle2, ConverterTool.SummarizeSizeSimple(_progressTotalSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressTotalSize)), + string.Format(Lang._GameRepairPage.PerProgressSubtitle2, ConverterTool.SummarizeSizeSimple(_progressAllSizeCurrent), ConverterTool.SummarizeSizeSimple(_progressAllSizeTotal)), true); // If asset type is unused, then delete it diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/StarRailRepair.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/StarRailRepair.cs index 3e5d1e47f..88d0a691e 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/StarRailRepair.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/StarRailRepair.cs @@ -100,7 +100,7 @@ private async Task CheckRoutine() // either way, returns false. return SummarizeStatusAndProgress( _assetIndex, - string.Format(Lang._GameRepairPage.Status3, _progressTotalCountFound, ConverterTool.SummarizeSizeSimple(_progressTotalSizeFound)), + string.Format(Lang._GameRepairPage.Status3, _progressAllCountFound, ConverterTool.SummarizeSizeSimple(_progressAllSizeFound)), Lang._GameRepairPage.Status4); } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml.cs index 29a6f02ce..da29d9a91 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml.cs @@ -162,8 +162,8 @@ private void _cacheTool_StatusChanged(object sender, TotalPerfileStatus e) CachesDataTableGrid.Visibility = e.IsAssetEntryPanelShow ? Visibility.Visible : Visibility.Collapsed; CachesStatus.Text = e.ActivityStatus; - CachesTotalStatus.Text = e.ActivityTotal; - CachesTotalProgressBar.IsIndeterminate = e.IsProgressTotalIndetermined; + CachesTotalStatus.Text = e.ActivityAll; + CachesTotalProgressBar.IsIndeterminate = e.IsProgressAllIndetermined; }); } @@ -171,7 +171,7 @@ private void _cacheTool_ProgressChanged(object sender, TotalPerfileProgress e) { DispatcherQueue?.TryEnqueue(() => { - CachesTotalProgressBar.Value = e.ProgressTotalPercentage; + CachesTotalProgressBar.Value = e.ProgressAllPercentage; }); } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs index e04aacfe9..bb9f7007d 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs @@ -750,7 +750,7 @@ await SpawnDialog( ContentDialogTheme.Error ); - public static async Task Dialog_ExistingDownload(UIElement Content, long partialLength, long contentLength) => + public static async Task Dialog_ExistingDownload(UIElement Content, double partialLength, double contentLength) => await SpawnDialog( Lang._Dialogs.InstallDataDownloadResumeTitle, string.Format(Lang._Dialogs.InstallDataDownloadResumeSubtitle, diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index d12fe471f..a8e468076 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -878,6 +878,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { - string InstallDownloadSpeedString = SummarizeSizeSimple(e.ProgressTotalSpeed); - string InstallDownloadSizeString = SummarizeSizeSimple(e.ProgressTotalDownload); - string InstallDownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileDownload); - string DownloadSizeString = SummarizeSizeSimple(e.ProgressTotalSizeToDownload); - string DownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeToDownload); + string InstallDownloadSpeedString = SummarizeSizeSimple(e.ProgressAllSpeed); + string InstallDownloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeCurrent); + string InstallDownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeCurrent); + string DownloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeTotal); + string DownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeTotal); ProgressPreStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, InstallDownloadSizeString, DownloadSizeString); ProgressPrePerFileStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, InstallDownloadPerSizeString, DownloadPerSizeString); ProgressPreStatusFooter.Text = string.Format(Lang._Misc.Speed, InstallDownloadSpeedString); - ProgressPreTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressTotalTimeLeft); - progressPreBar.Value = Math.Round(e.ProgressTotalPercentage, 2); + ProgressPreTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); + progressPreBar.Value = Math.Round(e.ProgressAllPercentage, 2); progressPrePerFileBar.Value = Math.Round(e.ProgressPerFilePercentage, 2); progressPreBar.IsIndeterminate = false; progressPrePerFileBar.IsIndeterminate = false; @@ -1169,6 +1180,7 @@ private void PreloadDownloadProgress(object sender, TotalPerfileProgress e) #region Game Install private async void InstallGameDialog(object sender, RoutedEventArgs e) { + bool isUseSophon = CurrentGameProperty._GameInstall.IsUseSophon; try { // Prevent device from sleep @@ -1182,13 +1194,23 @@ private async void InstallGameDialog(object sender, RoutedEventArgs e) progressRing.Value = 0; progressRing.IsIndeterminate = true; - ProgressStatusGrid.Visibility = Visibility.Visible; InstallGameBtn.Visibility = Visibility.Collapsed; CancelDownloadBtn.Visibility = Visibility.Visible; ProgressTimeLeft.Visibility = Visibility.Visible; - CurrentGameProperty._GameInstall.ProgressChanged += GameInstall_ProgressChanged; - CurrentGameProperty._GameInstall.StatusChanged += GameInstall_StatusChanged; + if (isUseSophon) + { + SophonProgressStatusGrid.Visibility = Visibility.Visible; + SophonProgressStatusSizeDownloadedGrid.Visibility = Visibility.Collapsed; + CurrentGameProperty._GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + } + else + { + ProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty._GameInstall.ProgressChanged += GameInstall_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged += GameInstall_StatusChanged; + } int dialogResult = await CurrentGameProperty._GameInstall.GetInstallationPath(); if (dialogResult < 0) @@ -1288,8 +1310,14 @@ private async void InstallGameDialog(object sender, RoutedEventArgs e) { IsSkippingUpdateCheck = false; CurrentGameProperty._GameInstall.StartAfterInstall = false; - CurrentGameProperty._GameInstall.ProgressChanged -= GameInstall_ProgressChanged; - CurrentGameProperty._GameInstall.StatusChanged -= GameInstall_StatusChanged; + + CurrentGameProperty._GameInstall.ProgressChanged -= isUseSophon ? + GameInstallSophon_ProgressChanged : + GameInstall_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged -= isUseSophon ? + GameInstallSophon_StatusChanged : + GameInstall_StatusChanged; + await Task.Delay(200); CurrentGameProperty._GameInstall.Flush(); ReturnToHomePage(); @@ -1312,7 +1340,7 @@ private void GameInstall_StatusChanged_Inner(TotalPerfileStatus e) ProgressStatusTitle.Text = e.ActivityStatus; progressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; - progressRing.IsIndeterminate = e.IsProgressTotalIndetermined; + progressRing.IsIndeterminate = e.IsProgressAllIndetermined; progressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; } @@ -1326,11 +1354,50 @@ private void GameInstall_ProgressChanged(object sender, TotalPerfileProgress e) private void GameInstall_ProgressChanged_Inner(TotalPerfileProgress e) { - progressRing.Value = e.ProgressTotalPercentage; + progressRing.Value = e.ProgressAllPercentage; progressRingPerFile.Value = e.ProgressPerFilePercentage; - ProgressStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressTotalDownload), SummarizeSizeSimple(e.ProgressTotalSizeToDownload)); - ProgressStatusFooter.Text = string.Format(Lang._Misc.Speed, SummarizeSizeSimple(e.ProgressTotalSpeed)); - ProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressTotalTimeLeft); + ProgressStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); + ProgressStatusFooter.Text = string.Format(Lang._Misc.Speed, SummarizeSizeSimple(e.ProgressAllSpeed)); + ProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); + } + + private void GameInstallSophon_StatusChanged(object sender, TotalPerfileStatus e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstallSophon_StatusChanged_Inner(sender, e); + else + DispatcherQueue?.TryEnqueue(() => GameInstallSophon_StatusChanged_Inner(sender, e)); + } + + private void GameInstallSophon_ProgressChanged(object sender, TotalPerfileProgress e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstallSophon_ProgressChanged_Inner(e); + else + DispatcherQueue?.TryEnqueue(() => GameInstallSophon_ProgressChanged_Inner(e)); + } + + private void GameInstallSophon_StatusChanged_Inner(object sender, TotalPerfileStatus e) + { + SophonProgressStatusTitleText.Text = e.ActivityStatus; + SophonProgressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; + + SophonProgressRing.IsIndeterminate = e.IsProgressAllIndetermined; + SophonProgressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; + } + + private void GameInstallSophon_ProgressChanged_Inner(TotalPerfileProgress e) + { + SophonProgressRing.Value = e.ProgressAllPercentage; + SophonProgressRingPerFile.Value = e.ProgressPerFilePercentage; + + SophonProgressStatusSizeTotalText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(Math.Max(e.ProgressAllSizeCurrent, 0)), SummarizeSizeSimple(e.ProgressAllSizeTotal)); + SophonProgressStatusSizeDownloadedText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(Math.Max(e.ProgressPerFileSizeCurrent, 0)), SummarizeSizeSimple(e.ProgressPerFileSizeTotal)); + + SophonProgressStatusSpeedTotalText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.ProgressAllSpeed)); + SophonProgressStatusSpeedDownloadedText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.ProgressPerFileSpeed)); + + SophonProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); } private void CancelInstallationProcedure(object sender, RoutedEventArgs e) @@ -1366,23 +1433,11 @@ private void CancelPreDownload() private void CancelUpdateDownload() { CurrentGameProperty._GameInstall.CancelRoutine(); - - /* - ProgressStatusGrid.Visibility = Visibility.Collapsed; - UpdateGameBtn.Visibility = Visibility.Visible; - CancelDownloadBtn.Visibility = Visibility.Collapsed; - */ } private void CancelInstallationDownload() { CurrentGameProperty._GameInstall.CancelRoutine(); - - /* - ProgressStatusGrid.Visibility = Visibility.Collapsed; - InstallGameBtn.Visibility = Visibility.Visible; - CancelDownloadBtn.Visibility = Visibility.Collapsed; - */ } #endregion @@ -2154,8 +2209,7 @@ private static void SavePlaytimeToRegistry(bool isPlaytime, string regionRegistr #region Game Update Dialog private async void UpdateGameDialog(object sender, RoutedEventArgs e) { - CurrentGameProperty._GameInstall.ProgressChanged += GameInstall_ProgressChanged; - CurrentGameProperty._GameInstall.StatusChanged += GameInstall_StatusChanged; + bool isUseSophon = CurrentGameProperty._GameInstall.IsUseSophon; HideImageCarousel(true); @@ -2168,14 +2222,20 @@ private async void UpdateGameDialog(object sender, RoutedEventArgs e) IsSkippingUpdateCheck = true; - ProgressStatusGrid.Visibility = Visibility.Visible; UpdateGameBtn.Visibility = Visibility.Collapsed; CancelDownloadBtn.Visibility = Visibility.Visible; - if (CurrentGameProperty._GameInstall.IsUseSophon) + if (isUseSophon) { - DownloadModeLabel.Visibility = Visibility.Visible; - DownloadModeLabelText.Text = Lang._Misc.DownloadModeLabelSophon; + SophonProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty._GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + } + else + { + ProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty._GameInstall.ProgressChanged += GameInstall_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged += GameInstall_StatusChanged; } int verifResult; @@ -2233,8 +2293,14 @@ private async void UpdateGameDialog(object sender, RoutedEventArgs e) { IsSkippingUpdateCheck = false; CurrentGameProperty._GameInstall.StartAfterInstall = false; - CurrentGameProperty._GameInstall.ProgressChanged -= GameInstall_ProgressChanged; - CurrentGameProperty._GameInstall.StatusChanged -= GameInstall_StatusChanged; + + CurrentGameProperty._GameInstall.ProgressChanged -= isUseSophon ? + GameInstallSophon_ProgressChanged : + GameInstall_ProgressChanged; + CurrentGameProperty._GameInstall.StatusChanged -= isUseSophon ? + GameInstallSophon_StatusChanged : + GameInstall_StatusChanged; + await Task.Delay(200); CurrentGameProperty._GameInstall.Flush(); ReturnToHomePage(); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/RepairPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/RepairPage.xaml.cs index 5a090fd18..d059e9855 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/RepairPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/RepairPage.xaml.cs @@ -164,8 +164,8 @@ private void _repairTool_StatusChanged(object sender, TotalPerfileStatus e) RepairStatus.Text = e.ActivityStatus; RepairPerFileStatus.Text = e.ActivityPerFile; - RepairTotalStatus.Text = e.ActivityTotal; - RepairTotalProgressBar.IsIndeterminate = e.IsProgressTotalIndetermined; + RepairTotalStatus.Text = e.ActivityAll; + RepairTotalProgressBar.IsIndeterminate = e.IsProgressAllIndetermined; RepairPerFileProgressBar.IsIndeterminate = e.IsProgressPerFileIndetermined; }); } @@ -175,7 +175,7 @@ private void _repairTool_ProgressChanged(object sender, TotalPerfileProgress e) DispatcherQueue?.TryEnqueue(() => { RepairPerFileProgressBar.Value = e.ProgressPerFilePercentage; - RepairTotalProgressBar.Value = e.ProgressTotalPercentage; + RepairTotalProgressBar.Value = e.ProgressAllPercentage; }); } diff --git a/Hi3Helper.Core/Lang/Locale/LangMisc.cs b/Hi3Helper.Core/Lang/Locale/LangMisc.cs index ac1604f75..294487fa8 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMisc.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMisc.cs @@ -59,6 +59,9 @@ public sealed class LangMisc public string LangNameCN { get; set; } = LangFallback?._Misc.LangNameCN; public string LangNameKR { get; set; } = LangFallback?._Misc.LangNameKR; public string Downloading { get; set; } = LangFallback?._Misc.Downloading; + public string Updating { get; set; } = LangFallback?._Misc.Updating; + public string UpdatingAndApplying { get; set; } = LangFallback?._Misc.UpdatingAndApplying; + public string Applying { get; set; } = LangFallback?._Misc.Applying; public string Merging { get; set; } = LangFallback?._Misc.Merging; public string Idle { get; set; } = LangFallback?._Misc.Idle; public string Change { get; set; } = LangFallback?._Misc.Change; diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 6c581c35a..44a53165e 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -586,6 +586,9 @@ "LangNameKR": "Korean", "Downloading": "Downloading", + "Updating": "Updating", + "UpdatingAndApplying": "Update + Applying", + "Applying": "Applying", "Merging": "Merging", "Idle": "Idle", "Change": "Change", From 6b2d4cefdeedcd3667e31eee8b7a7e87a14042e1 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sat, 27 Jul 2024 19:23:03 +0700 Subject: [PATCH 13/39] Fix aliased shadow under install progress ring --- CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index a8e468076..9e308d7af 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -302,7 +302,6 @@ - + Translation="0,0,48" /> + Translation="0,0,48" /> Date: Sat, 27 Jul 2024 19:28:01 +0700 Subject: [PATCH 14/39] Fix invisible border while button is disabled --- CollapseLauncher/App.xaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml index fc50603c9..3a1529922 100644 --- a/CollapseLauncher/App.xaml +++ b/CollapseLauncher/App.xaml @@ -1704,6 +1704,11 @@ + + + Date: Sat, 27 Jul 2024 19:39:12 +0700 Subject: [PATCH 15/39] Fix wrong values clamp --- CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index 1391efc1d..9171107bd 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -1391,11 +1391,11 @@ private void GameInstallSophon_ProgressChanged_Inner(TotalPerfileProgress e) SophonProgressRing.Value = e.ProgressAllPercentage; SophonProgressRingPerFile.Value = e.ProgressPerFilePercentage; - SophonProgressStatusSizeTotalText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(Math.Max(e.ProgressAllSizeCurrent, 0)), SummarizeSizeSimple(e.ProgressAllSizeTotal)); - SophonProgressStatusSizeDownloadedText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(Math.Max(e.ProgressPerFileSizeCurrent, 0)), SummarizeSizeSimple(e.ProgressPerFileSizeTotal)); + SophonProgressStatusSizeTotalText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); + SophonProgressStatusSizeDownloadedText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressPerFileSizeCurrent), SummarizeSizeSimple(e.ProgressPerFileSizeTotal)); - SophonProgressStatusSpeedTotalText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.ProgressAllSpeed)); - SophonProgressStatusSpeedDownloadedText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(e.ProgressPerFileSpeed)); + SophonProgressStatusSpeedTotalText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressAllSpeed, 0))); + SophonProgressStatusSpeedDownloadedText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressPerFileSpeed, 0))); SophonProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); } From 3d73d357034ee8783fc2946737ecd93a63833186 Mon Sep 17 00:00:00 2001 From: Bagus Nur Listiyono Date: Sat, 27 Jul 2024 22:40:51 +0700 Subject: [PATCH 16/39] [Feat] Add "hide playtime" feature Toggleable via QuickSettings, appwide. This will not disable the playtime counter mechanism, just hide the button where the playtime is shown. --- .../XAMLs/MainApp/Pages/HomePage.Variable.cs | 19 ++++++++++++++- .../XAMLs/MainApp/Pages/HomePage.xaml | 8 +++++++ .../XAMLs/MainApp/Pages/HomePage.xaml.cs | 24 +++++++++++++++++++ .../Classes/Shared/Region/LauncherConfig.cs | 1 + Hi3Helper.Core/Lang/Locale/LangHomePage.cs | 1 + Hi3Helper.Core/Lang/en_US.json | 1 + 6 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs index 036155ddc..6a945a486 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs @@ -61,6 +61,22 @@ public bool IsSocMedPanelShow } } + public bool IsPlaytimeBtnVisible + { + get + { + var v = GetAppConfigValue("ShowGamePlaytime").ToBoolNullable() ?? true; + TogglePlaytimeBtn(v); + + return v; + } + set + { + SetAndSaveConfigValue("ShowGamePlaytime", value); + TogglePlaytimeBtn(value); + } + } + public string NoNewsSplashMascot { get @@ -132,8 +148,9 @@ internal VerticalAlignment CurrentBannerIconVerticalAlign WindowSize.WindowSize.CurrentWindowSize.BannerIconAlignVerticalHYP; } - public void ToggleEventsPanel(bool hide) => HideImageCarousel(!hide); + public void ToggleEventsPanel(bool hide) => HideImageCarousel(!hide); public void ToggleSocmedPanelPanel(bool hide) => HideSocialMediaPanel(!hide); + public void TogglePlaytimeBtn(bool hide) => HidePlaytimeButton(!hide); } public class NullVisibilityConverter : IValueConverter diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 9e308d7af..5b4d88c1c 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -1791,6 +1791,14 @@ IsOn="{x:Bind IsSocMedPanelShow, Mode=TwoWay}" OffContent="{x:Bind helper:Locale.Lang._Misc.Disabled}" OnContent="{x:Bind helper:Locale.Lang._Misc.Enabled}" /> + + Date: Sun, 28 Jul 2024 15:39:10 +0700 Subject: [PATCH 25/39] Bump version --- CollapseLauncher/CollapseLauncher.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index 7fd659059..0adb355bc 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -15,7 +15,7 @@ $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) - 1.80.16 + 1.81.1 preview x64 From 779cb8276b6ae54a98e5d72505405d805557cf20 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 15:47:37 +0700 Subject: [PATCH 26/39] Fix crash if clean-up routine is performed after installation --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index ff3c7d26c..e0a242442 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -149,6 +149,9 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc LoadingMessageHelper.ShowLoadingFrame(); try { + // Reset token + ResetStatusAndProgress(); + // Initialize uninstall game property _uninstallGameProperty ??= AssignUninstallFolders(); if (!_uninstallGameProperty.HasValue) From 65c3257e9fd30fc1ed59e8678378102d96570518 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 15:47:58 +0700 Subject: [PATCH 27/39] Fix Http's Chunks not being detected on clean-up --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index e0a242442..3e12a18ff 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -365,10 +365,11 @@ protected virtual bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePat // 8th check: Ensure that the file is one of package files if (includeZipCheck && Regex.IsMatch(fileName, - @"(\.[0-9][0-9][0-9]|zip|7z|patch)$", + @"(\.\d\d\d|(zip|7z)|patch)|\.$", RegexOptions.Compiled | RegexOptions.NonBacktracking - )) return true; + )) + return true; // If all those matches failed, then return them as a non-game file return false; From 6a0815ab00cfeeb9b8d4d1e8ed1add867c287659 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 15:51:50 +0700 Subject: [PATCH 28/39] Fix thread getting stuck on deletion to recycle bin --- CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs index 62c6bcb9d..5c396c12d 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml.cs @@ -168,7 +168,7 @@ private async Task PerformRemoval(IList deletionSource, long tota { fileInfoN.IsReadOnly = false; if (isToRecycleBin) - InvokeProp.MoveFileToRecycleBin(fileInfoN.FullName); + await Task.Run(() => InvokeProp.MoveFileToRecycleBin(fileInfoN.FullName)); else fileInfoN.Delete(); } From f1efff372315b1007706069e3ca0c05d01789684 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 15:59:49 +0700 Subject: [PATCH 29/39] Check for file existance if _isSophonPreloadCompleted being set --- .../InstallManagement/BaseClass/InstallManagerBase.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index a3d2f89be..0ebf8b3f2 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -126,8 +126,11 @@ protected bool _isSophonPreloadCompleted return; } - fileInfo.IsReadOnly = false; - fileInfo.Delete(); + if (fileInfo.Exists) + { + fileInfo.IsReadOnly = false; + fileInfo.Delete(); + } } catch (Exception ex) { From 7deedbbca45a5c50ca315d070770c37748ce9318 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 16:18:07 +0700 Subject: [PATCH 30/39] Fix intermiten failure while parsing on HexStringToBytesConverter --- .../Classes/Helper/JsonConverter/HexStringToBytesConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs b/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs index af5a0301a..d14415736 100644 --- a/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs +++ b/CollapseLauncher/Classes/Helper/JsonConverter/HexStringToBytesConverter.cs @@ -21,7 +21,7 @@ public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS int bufferAllocLen = charSpan.Length >> 1; bool useRent = charSpan.Length <= 64 << 10; - byte[] returnBuffer = GC.AllocateUninitializedArray(bufferAllocLen); + byte[] returnBuffer = GC.AllocateUninitializedArray(bufferAllocLen, true); char[] stringBuffer = useRent ? ArrayPool.Shared.Rent(charSpan.Length) : new char[charSpan.Length]; try @@ -46,7 +46,7 @@ public override unsafe void Write(Utf8JsonWriter writer, byte[] value, JsonSeria throw new JsonException($"Bytes Array does not have an even length! (Length: {value.Length} characters)"); int bufferAllocLen = value.Length << 1; - char[] stringBuffer = GC.AllocateUninitializedArray(bufferAllocLen); + char[] stringBuffer = GC.AllocateUninitializedArray(bufferAllocLen, true); if (!HexTool.TryBytesToHexUnsafe(value, stringBuffer)) throw new JsonException("Failed while converting bytes to hex"); From 3bc986ab643e3704bcb829ab1ea766dca4d1f2e8 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 16:18:42 +0700 Subject: [PATCH 31/39] Allow deletion of Sophon chunk files on Files Clean-up --- .../BaseClass/InstallManagerBase.PkgVersion.cs | 18 +++++++++++++----- .../InstallManagement/Honkai/HonkaiInstall.cs | 14 +++++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 3e12a18ff..744e88b49 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -157,6 +157,9 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc if (!_uninstallGameProperty.HasValue) throw new NotSupportedException("Clean-up feature for this game is not yet supported!"); + // Get game state + GameInstallStateEnum gameStateEnum = await _gameVersionManager.GetGameState(); + // Do pkg_version check if Zip Check is used if (includeZipCheck) { @@ -166,7 +169,6 @@ protected virtual async Task> GetUnusedFileInfoList(bool inc Locale.Lang._FileCleanupPage.LoadingSubtitle2); using Http client = new Http(); - GameInstallStateEnum gameStateEnum = await _gameVersionManager.GetGameState(); RegionResourceVersion? packageLatestBase = _gameVersionManager .GetGameLatestZip(gameStateEnum).FirstOrDefault(); string? packageExtractBasePath = packageLatestBase?.decompressed_path; @@ -221,7 +223,7 @@ await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic, // Get the list of the local file paths List localFileInfo = new List(); - await GetRelativeLocalFilePaths(localFileInfo, includeZipCheck, _token.Token); + await GetRelativeLocalFilePaths(localFileInfo, includeZipCheck, gameStateEnum, _token.Token); // Get and filter the unused file from the pkg_versions List unusedFileInfo = new List(); @@ -311,7 +313,7 @@ protected virtual async ValueTask InnerParsePkgVersion2FileInfo(string gamePath, } } - protected virtual bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, out LocalFileInfo localFileInfo) + protected virtual bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, GameInstallStateEnum gameState, out LocalFileInfo localFileInfo) { // Convert to LocalFileInfo and get the relative path localFileInfo = new LocalFileInfo(fileInfo, gamePath); @@ -371,11 +373,17 @@ protected virtual bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePat )) return true; + // 9th check: Ensure that the file is Sophon Chunk file + // if game state is installed. + if (gameState == GameInstallStateEnum.Installed + && localFileInfo.RelativePath.StartsWith("chunk_collapse", StringComparison.OrdinalIgnoreCase)) + return true; + // If all those matches failed, then return them as a non-game file return false; } - protected virtual async Task GetRelativeLocalFilePaths(List localFileInfoList, bool includeZipCheck, CancellationToken token) + protected virtual async Task GetRelativeLocalFilePaths(List localFileInfoList, bool includeZipCheck, GameInstallStateEnum gameState, CancellationToken token) { await Task.Run(() => { @@ -395,7 +403,7 @@ await Task.Run(() => // Do the check within the lambda function to possibly check the file // condition in multithread - if (IsCategorizedAsGameFile(fileInfo, gamePath, includeZipCheck, out LocalFileInfo localFileInfo)) + if (IsCategorizedAsGameFile(fileInfo, gamePath, includeZipCheck, gameState, out LocalFileInfo localFileInfo)) { Interlocked.Add(ref totalSize, fileInfo.Exists ? fileInfo.Length : 0); Interlocked.Increment(ref count); diff --git a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs index bb84f10f7..397970cbd 100644 --- a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs @@ -3,6 +3,7 @@ using CollapseLauncher.Interfaces; using CollapseLauncher.Pages; using Hi3Helper; +using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System; @@ -132,7 +133,7 @@ private string GetFailedGameConversionFolder(string basepath) #endregion #region Override Methods - CleanUpGameFiles - protected override bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, out LocalFileInfo localFileInfo) + protected override bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePath, bool includeZipCheck, GameInstallStateEnum gameState, out LocalFileInfo localFileInfo) { // Convert to LocalFileInfo and get the relative path localFileInfo = new LocalFileInfo(fileInfo, gamePath); @@ -180,10 +181,17 @@ protected override bool IsCategorizedAsGameFile(FileInfo fileInfo, string gamePa // 7th check: Ensure that the file is one of package files if (includeZipCheck && Regex.IsMatch(fileName, - @"(\.[0-9][0-9][0-9]|zip|7z|patch)$", + @"(\.\d\d\d|(zip|7z)|patch)|\.$", RegexOptions.Compiled | RegexOptions.NonBacktracking - )) return true; + )) + return true; + + // 8th check: Ensure that the file is Sophon Chunk file + // if game state is installed. + if (gameState == GameInstallStateEnum.Installed + && localFileInfo.RelativePath.StartsWith("chunk_collapse", StringComparison.OrdinalIgnoreCase)) + return true; // If all those matches failed, then return them as a non-game file return false; From d219485218f85dbcfda0f74237e39d25a9776f00 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 16:35:13 +0700 Subject: [PATCH 32/39] Disable clean-up feature on update --- CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index f2b49aa83..c74f171d3 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -846,6 +846,7 @@ private async ValueTask GetCurrentGameState() { RepairGameButton.Visibility = RepairGameButtonVisible; RepairGameButton.IsEnabled = false; + CleanupFilesButton.IsEnabled = false; UpdateGameBtn.Visibility = Visibility.Visible; StartGameBtn.Visibility = Visibility.Collapsed; InstallGameBtn.Visibility = Visibility.Collapsed; From d7e0c2ae4aad194cdd164caad688137960265cc0 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 19:30:56 +0700 Subject: [PATCH 33/39] Fix disposal issue on corrupted old reference file --- Hi3Helper.Sophon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hi3Helper.Sophon b/Hi3Helper.Sophon index edab55cb0..762728b2a 160000 --- a/Hi3Helper.Sophon +++ b/Hi3Helper.Sophon @@ -1 +1 @@ -Subproject commit edab55cb02d325510e01cb0111f2857851032571 +Subproject commit 762728b2ac1d396933422e6cb12649e26d6c90c3 From 309b40e1a981e82f75092885eff6ac388c200da4 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Sun, 28 Jul 2024 19:52:39 +0700 Subject: [PATCH 34/39] Enable Http Override for Cache Update --- .../Classes/CachesManagement/Honkai/Update.cs | 8 +++++++- .../Classes/CachesManagement/StarRail/Update.cs | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs index bfd881fa0..a405eb256 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs @@ -27,7 +27,13 @@ private async Task Update(List updateAssetIndex, List Update(List updateAssetIndex, List as // Subscribe the event listener httpClient.DownloadProgress += _httpClient_UpdateAssetProgress; + // Iterate the asset index and do update operation - foreach (SRAsset asset in updateAssetIndex) + foreach (SRAsset asset in +#if ENABLEHTTPREPAIR + EnforceHTTPSchemeToAssetIndex(updateAssetIndex!) +#else + updateAssetIndex! +#endif + ) { await UpdateCacheAsset(asset, httpClient, token); } From ee27eba714012bc22c4fa71996e8415fde6654b7 Mon Sep 17 00:00:00 2001 From: Kemal Setya Adhi Date: Mon, 29 Jul 2024 01:01:55 +0700 Subject: [PATCH 35/39] Minor UI Improvements + Moving NavigationItem Font Icon from FontAwesome to Segoe Fluent Icons + Moving the Game Settings menu button to the bottom of the Navigation Bar + Fix Potential Crash during AttachedShadow assignment + Adding Shadow to All Icons in the Navigation Bar + Adding Outlined Logo asset + Scaling out Social Media Icons when hovered + Adjust some UI Brightness for Light Theme --- CollapseLauncher/App.xaml | 92 +++++++++++++++--- .../CollapseLauncherLogoOutlineDark.png | Bin 0 -> 6449 bytes .../CollapseLauncherLogoOutlineLight.png | Bin 0 -> 26078 bytes .../Classes/Extension/UIElementExtensions.cs | 17 ++-- .../RegionManagement/RegionManagement.cs | 16 ++- CollapseLauncher/XAMLs/MainApp/MainPage.xaml | 2 +- .../XAMLs/MainApp/MainPage.xaml.cs | 74 +++++++++----- .../XAMLs/MainApp/MainWindow.xaml | 4 +- .../XAMLs/MainApp/Pages/HomePage.xaml.cs | 84 +++++++++------- Hi3Helper.Core/packages.lock.json | 9 +- InnoSetupHelper/packages.lock.json | 9 +- 11 files changed, 215 insertions(+), 92 deletions(-) create mode 100644 CollapseLauncher/Assets/CollapseLauncherLogoOutlineDark.png create mode 100644 CollapseLauncher/Assets/CollapseLauncherLogoOutlineLight.png diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml index 22feccc85..750651a23 100644 --- a/CollapseLauncher/App.xaml +++ b/CollapseLauncher/App.xaml @@ -19,6 +19,8 @@ + ms-appx:///Assets/CollapseLauncherLogo.png + ms-appx:///Assets/CollapseLauncherLogoOutlineLight.png #ffd52a #ffd52a @@ -27,6 +29,9 @@ #ffd52a + + + + + + Color="#40000000"/> + + + + + + + + + + + + + @@ -338,6 +369,8 @@ + ms-appx:///Assets/CollapseLauncherLogo.png + ms-appx:///Assets/CollapseLauncherLogoOutlineDark.png #693758 #693758 @@ -346,6 +379,9 @@ #693758 + + + + TintLuminosityOpacity="0.7" + TintOpacity="0.0"/> + + Color="#66FFFFFF"/> + + + + + + + + + + + + @@ -613,24 +673,24 @@ + Value="{ThemeResource SolidColorBrushBackgroundHover}"/> @@ -1544,7 +1604,7 @@ + Value="{ThemeResource SolidColorBrushBackgroundPressed}"/> @@ -1567,7 +1627,7 @@ + Value="{ThemeResource SolidColorBrushBackgroundDisabled}"/> diff --git a/CollapseLauncher/Assets/CollapseLauncherLogoOutlineDark.png b/CollapseLauncher/Assets/CollapseLauncherLogoOutlineDark.png new file mode 100644 index 0000000000000000000000000000000000000000..67ca7b603c785ca04ccd61a9aaec21befea7180f GIT binary patch literal 6449 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 literal 0 HcmV?d00001 diff --git a/CollapseLauncher/Assets/CollapseLauncherLogoOutlineLight.png b/CollapseLauncher/Assets/CollapseLauncherLogoOutlineLight.png new file mode 100644 index 0000000000000000000000000000000000000000..231aced8ffaecda7e4669cd0341728d71d4f2286 GIT binary patch literal 26078 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 literal 0 HcmV?d00001 diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index 5321228ee..0c9f13683 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -704,15 +704,18 @@ void AttachShadow(FrameworkElement thisElement, bool innerMask, Vector3? offset) if (xamlRoot is Border borderParent) xamlRoot = borderParent.Child is Grid grid ? grid : borderParent.Child.FindAscendant(); - (xamlRoot as Panel).Children.Add(shadowPanel); - Canvas.SetZIndex(shadowPanel, -1); - if (shadowPanel is not Panel) - throw new NotSupportedException("The ShadowGrid must be at least a Grid or StackPanel or any \"Panel\" elements"); + if (xamlRoot is Panel panel) + { + panel.Children.Add(shadowPanel); + Canvas.SetZIndex(shadowPanel, -1); + if (shadowPanel is not Panel) + throw new NotSupportedException("The ShadowGrid must be at least a Grid or StackPanel or any \"Panel\" elements"); - if (xamlRoot == null || xamlRoot is not Panel) - throw new NullReferenceException("The element must be inside of a Grid or StackPanel or any \"Panel\" elements"); + if (xamlRoot == null || xamlRoot is not Panel) + throw new NullReferenceException("The element must be inside of a Grid or StackPanel or any \"Panel\" elements"); - thisElement.ApplyDropShadow(shadowPanel, shadowColor, blurRadius, opacity, innerMask, offset); + thisElement.ApplyDropShadow(shadowPanel, shadowColor, blurRadius, opacity, innerMask, offset); + } } } diff --git a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs index 60e1530f7..4992938cb 100644 --- a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs +++ b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs @@ -44,7 +44,8 @@ private enum ResourceLoadingType private const uint LoadTimeoutStep = 5; // Step 5 seconds for each timeout retries private static string RegionToChangeName { get => $"{GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameName, Lang._GameClientTitles)} - {GetGameTitleRegionTranslationString(LauncherMetadataHelper.CurrentMetadataConfigGameRegion, Lang._GameClientRegions)}"; } - private List LastNavigationItem; + private List LastMenuNavigationItem; + private List LastFooterNavigationItem; internal static string PreviousTag = string.Empty; internal async Task LoadRegionFromCurrentConfigV2(PresetConfig preset, string gameName, string gameRegion) @@ -111,12 +112,17 @@ async void CancelLoadEvent(object sender, RoutedEventArgs args) await tokenSource.CancelAsync(); // If explicit cancel was triggered, restore the navigation menu item then return false - foreach (object item in LastNavigationItem) + foreach (object item in LastMenuNavigationItem) { NavigationViewControl.MenuItems.Add(item); } + foreach (object item in LastFooterNavigationItem) + { + NavigationViewControl.FooterMenuItems.Add(item); + } NavigationViewControl.IsSettingsVisible = true; - LastNavigationItem.Clear(); + LastMenuNavigationItem.Clear(); + LastFooterNavigationItem.Clear(); if (m_arguments.StartGame != null) m_arguments.StartGame.Play = false; @@ -143,8 +149,10 @@ void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int time public void ClearMainPageState() { // Clear NavigationViewControl Items and Reset Region props - LastNavigationItem = [..NavigationViewControl.MenuItems]; + LastMenuNavigationItem = [..NavigationViewControl.MenuItems]; + LastFooterNavigationItem = [..NavigationViewControl.FooterMenuItems]; NavigationViewControl.MenuItems.Clear(); + NavigationViewControl.FooterMenuItems.Clear(); NavigationViewControl.IsSettingsVisible = false; PreviousTag = "launcher"; PreviousTagString.Clear(); diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index a3c33eaf0..c1bd7cd01 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -347,7 +347,7 @@ Margin="-2,-2,0,-2" VerticalAlignment="Stretch" Opacity="0.8" - Source="ms-appx:///Assets/CollapseLauncherLogo.png"> + Source="{ThemeResource AppLogo}"> diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index 1b25f089e..58a9a6611 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -201,6 +201,7 @@ private async Task InitializeStartup() private void UpdateBindingsEvent(object sender, EventArgs e) { NavigationViewControl.MenuItems.Clear(); + NavigationViewControl.FooterMenuItems.Clear(); Bindings.Update(); UpdateLayout(); @@ -1185,29 +1186,17 @@ private void InitializeNavigationItems(bool ResetSelection = true) { NavigationViewControl.IsSettingsVisible = true; NavigationViewControl.MenuItems.Clear(); + NavigationViewControl.FooterMenuItems.Clear(); IGameVersionCheck CurrentGameVersionCheck = GetCurrentGameProperty()._GameVersion; FontFamily Fnt = FontCollections.FontAwesomeSolid; - FontIcon IconLauncher = new FontIcon { FontFamily = Fnt, Glyph = "" }; - FontIcon IconRepair = new FontIcon { FontFamily = Fnt, Glyph = "" }; - FontIcon IconCaches = new FontIcon { FontFamily = Fnt, Glyph = "" }; - FontIcon IconGameSettings = new FontIcon { FontFamily = Fnt, Glyph = "" }; - FontIcon IconAppSettings = new FontIcon { FontFamily = Fnt, Glyph = "" }; - - IconLauncher.ApplyDropShadow(Colors.Gray, 20); - IconRepair.ApplyDropShadow(Colors.Gray, 20); - IconCaches.ApplyDropShadow(Colors.Gray, 20); - IconGameSettings.ApplyDropShadow(Colors.Gray, 20); - IconAppSettings.ApplyDropShadow(Colors.Gray, 20); - - if (NavigationViewControl.SettingsItem is not null && NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) - { - SettingsItem.Content = Lang._SettingsPage.PageTitle; - SettingsItem.Icon = IconAppSettings; - ToolTipService.SetToolTip(SettingsItem, Lang._SettingsPage.PageTitle); - } + FontIcon IconLauncher = new FontIcon { Glyph = "" }; + FontIcon IconRepair = new FontIcon { Glyph = "" }; + FontIcon IconCaches = new FontIcon { Glyph = "" }; + FontIcon IconGameSettings = new FontIcon { Glyph = "" }; + FontIcon IconAppSettings = new FontIcon { Glyph = "" }; if (m_appMode == AppMode.Hi3CacheUpdater) { @@ -1239,29 +1228,55 @@ private void InitializeNavigationItems(bool ResetSelection = true) switch (CurrentGameVersionCheck.GameType) { case GameNameType.Honkai: - NavigationViewControl.MenuItems.Add(new NavigationViewItem() + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() { Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "honkaigamesettings" }); break; case GameNameType.StarRail: - NavigationViewControl.MenuItems.Add(new NavigationViewItem() + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() { Content = Lang._StarRailGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "starrailgamesettings" }); break; case GameNameType.Genshin: - NavigationViewControl.MenuItems.Add(new NavigationViewItem() + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() { Content = Lang._GenshinGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "genshingamesettings" }); break; case GameNameType.Zenless: - NavigationViewControl.MenuItems.Add(new NavigationViewItem() + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() {Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "zenlessgamesettings"}); break; } + if (NavigationViewControl.SettingsItem is not null && NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) + { + SettingsItem.Content = Lang._SettingsPage.PageTitle; + SettingsItem.Icon = IconAppSettings; + ToolTipService.SetToolTip(SettingsItem, Lang._SettingsPage.PageTitle); + } + + foreach (var deps in NavigationViewControl.FindDescendants()) + { + if (deps is FontIcon icon) + AttachShadowNavigationPanelItem(icon); + if (deps is AnimatedIcon animIcon) + AttachShadowNavigationPanelItem(animIcon); + } + AttachShadowNavigationPanelItem(IconAppSettings); + if (ResetSelection) { NavigationViewControl.SelectedItem = (NavigationViewItem)NavigationViewControl.MenuItems[0]; } } + public static void AttachShadowNavigationPanelItem(FrameworkElement element) + { + bool isAppLight = IsAppThemeLight; + Windows.UI.Color shadowColor = isAppLight ? Colors.White : Colors.Black; + double shadowBlurRadius = isAppLight ? 20 : 15; + double shadowOpacity = isAppLight ? 0.5 : 0.3; + + element.ApplyDropShadow(shadowColor, shadowBlurRadius, shadowOpacity); + } + private void NavView_Loaded(object sender, RoutedEventArgs e) { foreach (NavigationViewItemBase item in NavigationViewControl.MenuItems) @@ -1321,8 +1336,11 @@ private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvoke if (!IsLoadFrameCompleted) return; if (args.IsSettingsInvoked && PreviousTag != "settings") Navigate(typeof(SettingsPage), "settings"); - NavigationViewItem item = sender.MenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); +#nullable enable + NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); + item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); if (item == null) return; +#nullable restore string itemTag = (string)item.Tag; @@ -1447,7 +1465,11 @@ private void NavigationViewControl_BackRequested(NavigationView sender, Navigati if (lastPreviousTag.ToLower() == currentNavigationItemTag.ToLower()) { string goLastPreviousTag = PreviousTagString[PreviousTagString.Count - 2]; - NavigationViewItem goPreviousNavigationItem = sender.MenuItems.OfType().Where(x => goLastPreviousTag == (string)x.Tag).FirstOrDefault(); + +#nullable enable + NavigationViewItem? goPreviousNavigationItem = sender.MenuItems.OfType().Where(x => goLastPreviousTag == (string)x.Tag).FirstOrDefault(); + goPreviousNavigationItem ??= sender.FooterMenuItems.OfType().Where(x => goLastPreviousTag == (string)x.Tag).FirstOrDefault(); +#nullable restore if (goLastPreviousTag == "settings") { @@ -1941,11 +1963,11 @@ private void GoGameSettings_Invoked(KeyboardAccelerator sender, KeyboardAccelera if (!(IsLoadRegionComplete) || CannotUseKbShortcuts) return; - if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems.Last()) + if (NavigationViewControl.SelectedItem == NavigationViewControl.FooterMenuItems.Last()) return; DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems.Last(); + NavigationViewControl.SelectedItem = NavigationViewControl.FooterMenuItems.Last(); switch (CurrentGameProperty._GamePreset.GameType) { case GameNameType.Honkai: diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml index aaf30b94c..f9a52cac7 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml @@ -171,7 +171,7 @@ Click="MinimizeButton_Click" Content="M 0 0 H 10" CornerRadius="8" - Style="{StaticResource WindowCaptionButton}" /> + Style="{ThemeResource WindowCaptionButton}" />