diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index 0e4b36ab29..4e8551cbff 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -4,6 +4,7 @@ using CollapseLauncher.RepairManagement; using Hi3Helper; using Hi3Helper.EncTool; +using Hi3Helper.EncTool.Enc; using Hi3Helper.EncTool.Parser.KianaDispatch; using Hi3Helper.UABT; using System; @@ -161,7 +162,7 @@ private IEnumerable EnumerateCacheTextAsset(CacheAssetType type, IEn { // Set isFirst flag as true if type is Data and // also convert type as lowered string. - bool isFirst = type == CacheAssetType.Data; + bool isFirst = type == CacheAssetType.Data; bool isNeedReadLuckyNumber = type == CacheAssetType.Data; foreach (string line in enumerator) @@ -178,9 +179,8 @@ private IEnumerable EnumerateCacheTextAsset(CacheAssetType type, IEn } // Get the lucky number if it does so 👀 - if (isNeedReadLuckyNumber && int.TryParse(line, null, out int luckyNumber)) + if (isNeedReadLuckyNumber && int.TryParse(line, null, out int _)) { - LuckyNumber = luckyNumber; isNeedReadLuckyNumber = false; continue; } @@ -297,7 +297,7 @@ await Parallel.ForEachAsync(EnumerateCacheTextAsset(type, dataTextAsset.GetStrin return (count, size); } - private byte[] GetAssetIndexSalt(string data) + private static byte[] GetAssetIndexSalt(string data) { // Get the salt from the string and return as byte[] byte[] key; @@ -314,8 +314,15 @@ private byte[] GetAssetIndexSalt(string data) key = MetadataHelper.CurrentMasterKey?.Key; } - MhyEncTool saltTool = new(data, key); - return saltTool.GetSalt(); + byte[] saltBytes = new byte[8]; + if (!MhyEncTool.TryGetSalt(data, + key, + saltBytes, + out _, + out Exception exception)) + throw exception; + + return saltBytes; } private string GetAssetBasePathByType(CacheAssetType type) => Path.Combine(GamePath!, type == CacheAssetType.Data ? "Data" : "Resources"); @@ -328,7 +335,5 @@ private static bool IsValidRegionFile(string input, string lang) input.Contains($"{CacheRegionalCheckName}_{lang}"); // If none, then pass it as true (non-regional string) } - - public KianaDispatch GetCurrentGateway() => GameGateway; } } \ No newline at end of file diff --git a/CollapseLauncher/Classes/DiscordPresence/DiscordPresenceManager.cs b/CollapseLauncher/Classes/DiscordPresence/DiscordPresenceManager.cs index f1fd494ca4..d4f172e4e0 100644 --- a/CollapseLauncher/Classes/DiscordPresence/DiscordPresenceManager.cs +++ b/CollapseLauncher/Classes/DiscordPresence/DiscordPresenceManager.cs @@ -3,6 +3,7 @@ using CollapseLauncher.Helper.Update; using CollapseLauncher.Plugins; using DiscordRPC; +using DiscordRPC.Entities; using DiscordRPC.Message; using Hi3Helper; using System; @@ -139,9 +140,9 @@ private void EnablePresence(ulong applicationId) Logger.LogWriteLine("Discord Presence is Enabled!"); } - private void OnReady(object? _, ReadyMessage msg) + private void OnReady(object? sender, ReadyMessage? msg) { - Logger.LogWriteLine($"Connected to Discord with user {msg.User.Username}"); + Logger.LogWriteLine($"Connected to Discord with user {msg?.User?.Username}"); if (!_firstTimeConnect) { // Restart Discord RPC client @@ -161,9 +162,9 @@ private void OnReady(object? _, ReadyMessage msg) } } - private static void OnPresenceUpdate(object? _, PresenceMessage msg) + private static void OnPresenceUpdate(object? sender, PresenceMessage? msg) { - if (msg.Presence == null) + if (msg?.Presence == null) { Logger.LogWriteLine("Activity cleared!"); } diff --git a/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs b/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs index a5e4a97d27..2ec01077cf 100644 --- a/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs +++ b/CollapseLauncher/Classes/EventsManagement/BackgroundActivityManager.cs @@ -7,13 +7,11 @@ using CollapseLauncher.Plugins; using Hi3Helper; using Hi3Helper.Data; -using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using System; using System.Collections.Generic; -using System.IO; using System.Numerics; // ReSharper disable StringLiteralTypo diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.cs index a3b4b0fd3c..548e78c355 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.cs @@ -205,19 +205,15 @@ internal static async Task InitPluginComAsync(this T initializableInterface, throw new COMException($"Initialization for {nameof(T)} has failed with return code: {returnCode}", initTask.Exception); } + // ReSharper disable once InconsistentNaming private static readonly Guid IInitializableIid = new(ComInterfaceId.ExInitializable); private static IInitializableTask GetInitializableTask(T instance) where T : class { - if (!ComMarshal.TryCastComObjectAs(instance, - in IInitializableIid, - out IInitializableTask? initTask, - out Exception? ex)) - { - throw ex; - } - - return initTask; + return !ComMarshal.TryCastComObjectAs(instance, + in IInitializableIid, + out IInitializableTask? initTask, + out Exception? ex) ? throw ex : initTask; } internal static async Task GetResultFromAction(this Task task, Action getResultAction) diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs index c25225b892..ec23e803fe 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.UnsafeAccessorExtensions.cs @@ -15,7 +15,31 @@ public static partial class UIElementExtensions /// The member of an element /// The cursor you want to set. Use to choose the cursor you want to set. [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")] - internal static extern void SetCursor(this UIElement element, InputCursor inputCursor); + private static extern void SetCursorInner(this UIElement element, InputCursor inputCursor); + + /// + /// Set the cursor for the element. + /// + /// The member of an element + /// The cursor you want to set. Use to choose the cursor you want to set. + internal static T SetCursor(this T element, InputCursor inputCursor) + where T : UIElement + { + element.SetCursorInner(inputCursor); + return element; + } + + /// + /// Set the cursor for the element. + /// + /// The member of an element + /// The cursor you want to set. Use to choose the cursor you want to set. + internal static T SetCursor(this T element, InputSystemCursorShape inputCursor) + where T : UIElement + { + element.SetCursorInner(InputSystemCursor.Create(inputCursor)); + return element; + } /// /// Set the cursor for the element. @@ -28,6 +52,17 @@ internal static T WithCursor(this T element, InputCursor inputCursor) where T return element; } + /// + /// Set the cursor for the element. + /// + /// The member of an element + /// The cursor you want to set. Use to choose the cursor you want to set. + internal static T WithCursor(this T element, InputSystemCursorShape inputCursor) where T : UIElement + { + element.SetCursor(inputCursor); + return element; + } + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposedFlags")] private static extern ref int GetObjectReferenceDisposeFlags(this IObjectReference obj); diff --git a/CollapseLauncher/Classes/Extension/VelopackLocatorExtension.cs b/CollapseLauncher/Classes/Extension/VelopackLocatorExtension.cs index 70bb13f9d3..ed121fff0d 100644 --- a/CollapseLauncher/Classes/Extension/VelopackLocatorExtension.cs +++ b/CollapseLauncher/Classes/Extension/VelopackLocatorExtension.cs @@ -5,9 +5,7 @@ using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using Hi3Helper.Win32.ShellLinkCOM; -using NuGet.Versioning; using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -34,12 +32,8 @@ internal static void StartUpdaterHook(string aumid) // its custom AUMID IVelopackLogger? logger = ILoggerHelper.GetILogger("Velopack").ToVelopackLogger(); - Process currentProcess = Process.GetCurrentProcess(); - - WindowsVelopackLocator locator = - new(currentProcess.MainModule!.FileName, - (uint)currentProcess.Id, - logger); + DefaultProcessImpl procDefault = new(logger); + WindowsVelopackLocator locator = new(procDefault, logger); // HACK: Always ensure to set the AUMID field null so it won't // set the AUMID to its own. locator.GetLocatorAumidField() = null; @@ -168,7 +162,7 @@ public static void TryCleanupFallbackUpdate(SemanticVersion newVersion) // Try open the shortcut and check whether this shortcut is actually pointing to // CollapseLauncher.exe file - using ShellLink shellLink = new(thisUserStartMenuShortcut); + ShellLink shellLink = new(thisUserStartMenuShortcut); // Try to get the target path and its filename string shortcutTargetPath = shellLink.Target; if (!shortcutTargetPath.Equals(currentExecutedPath, StringComparison.OrdinalIgnoreCase)) diff --git a/CollapseLauncher/Classes/Extension/WriteableBitmapExtension.cs b/CollapseLauncher/Classes/Extension/WriteableBitmapExtension.cs index 2cabdd0159..b57786d3cd 100644 --- a/CollapseLauncher/Classes/Extension/WriteableBitmapExtension.cs +++ b/CollapseLauncher/Classes/Extension/WriteableBitmapExtension.cs @@ -1,6 +1,5 @@ using Hi3Helper; using Hi3Helper.SentryHelper; -using Hi3Helper.Win32.ManagedTools; using Hi3Helper.Win32.Native.Interfaces; using Hi3Helper.Win32.WinRT.IBufferCOM; using Microsoft.UI.Xaml.Media.Imaging; @@ -9,29 +8,27 @@ #pragma warning disable IDE0130 #nullable enable -namespace CollapseLauncher.Extension +namespace CollapseLauncher.Extension; + +internal static class WriteableBitmapExtension { - internal static class WriteableBitmapExtension + internal static nint GetBufferPointer(this WriteableBitmap writeableBitmap, out uint length) { - internal static nint GetBufferPointer(this WriteableBitmap writeableBitmap, out uint length) + length = writeableBitmap.PixelBuffer.Length; + try { - length = writeableBitmap.PixelBuffer.Length; IBufferByteAccess byteAccess = writeableBitmap.PixelBuffer.AsBufferByteAccess(); - try - { - byteAccess.Buffer(out nint bufferP); - return bufferP; - } - finally - { - if (!ComMarshal.TryReleaseComObject(byteAccess, out Exception? ex)) - { - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - Logger.LogWriteLine($"Cannot free the instance of IBufferByteAccess from WriteableBitmap\r\n{ex}", - LogType.Error, - true); - } - } + byteAccess.Buffer(out nint bufferP); + return bufferP; + } + catch (Exception ex) + { + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + Logger.LogWriteLine($"Cannot free the instance of IBufferByteAccess from WriteableBitmap\r\n{ex}", + LogType.Error, + true); } + + return nint.Zero; } } diff --git a/CollapseLauncher/Classes/FileDialog/FileDialogHelper.cs b/CollapseLauncher/Classes/FileDialog/FileDialogHelper.cs index f41f34ef86..fe07135650 100644 --- a/CollapseLauncher/Classes/FileDialog/FileDialogHelper.cs +++ b/CollapseLauncher/Classes/FileDialog/FileDialogHelper.cs @@ -22,7 +22,7 @@ internal static class FileDialogHelper /// The title of the folder dialog /// Override path check and returns the actual string /// If is empty, then it's cancelled. Otherwise, returns a selected path - internal static async Task GetRestrictedFolderPathDialog(string? title = null, Func>? checkOverride = null) + internal static async Task GetRestrictedFolderPathDialog(string? title = null, Func>? checkOverride = null) { StartGet: string dirPath = await FileDialogNative.GetFolderPicker(title); diff --git a/CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs b/CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs index a9e4166105..0a83f8e28b 100644 --- a/CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs +++ b/CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs @@ -94,18 +94,18 @@ private async Task StartRoutineInner(FileMigrationProcessUIRef uiRef) private async Task MoveFile(FileMigrationProcessUIRef uiRef) { - FileInfo inputPathInfo = new FileInfo(InputPath); - FileInfo outputPathInfo = new FileInfo(OutputPath); + FileInfo inputPathInfo = new(InputPath); + FileInfo outputPathInfo = new(OutputPath); - var inputPathDir = FileDialogHelper.IsRootPath(InputPath) + string inputPathDir = FileDialogHelper.IsRootPath(InputPath) ? Path.GetPathRoot(InputPath) : Path.GetDirectoryName(inputPathInfo.FullName); if (string.IsNullOrEmpty(inputPathDir)) - throw new InvalidOperationException(string.Format(Locale.Current.Lang?._Dialogs?.InvalidGameDirNewTitleFormat, + throw new InvalidOperationException(string.Format(Locale.Current.Lang?._Dialogs?.InvalidGameDirNewTitleFormat ?? "", InputPath)); - DirectoryInfo outputPathDirInfo = new DirectoryInfo(inputPathDir); + DirectoryInfo outputPathDirInfo = new(inputPathDir); outputPathDirInfo.Create(); // Update path display @@ -129,8 +129,8 @@ private async Task MoveFile(FileMigrationProcessUIRef uiRef) private async Task MoveDirectory(FileMigrationProcessUIRef uiRef) { - DirectoryInfo inputPathInfo = new DirectoryInfo(InputPath); - DirectoryInfo outputPathInfo = new DirectoryInfo(OutputPath); + DirectoryInfo inputPathInfo = new(InputPath); + DirectoryInfo outputPathInfo = new(OutputPath); outputPathInfo.Create(); bool isMoveBackward = inputPathInfo.FullName.StartsWith(outputPathInfo.FullName, StringComparison.OrdinalIgnoreCase) && @@ -148,7 +148,7 @@ private async Task MoveDirectory(FileMigrationProcessUIRef uiRef) // reduce massive seeking, hence improving speed. bool isBothSsd = DriveTypeChecker.IsDriveSsd(InputPath) && DriveTypeChecker.IsDriveSsd(OutputPath); - ParallelOptions parallelOptions = new ParallelOptions + ParallelOptions parallelOptions = new() { CancellationToken = TokenSource?.Token ?? CancellationToken.None, MaxDegreeOfParallelism = isBothSsd ? LauncherConfig.AppCurrentThread : 1 @@ -238,7 +238,7 @@ async ValueTask Impl(FileInfo inputFileInfo, CancellationToken cancellationToken else { Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Moving directory content across different drives from: {inputFileInfo.FullName} to {outputNewFilePath}", LogType.Default, true); - FileInfo outputFileInfo = new FileInfo(outputNewFilePath); + FileInfo outputFileInfo = new(outputNewFilePath); await MoveWriteFile(uiRef, inputFileInfo, outputFileInfo, cancellationToken); } } diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/DummyGameSettings.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/DummyGameSettings.cs index 596e4413c7..33d998184b 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/DummyGameSettings.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/DummyGameSettings.cs @@ -7,11 +7,6 @@ namespace CollapseLauncher.GameSettings; internal class DummyGameSettings : SettingsBase { - public DummyGameSettings() - { - - } - public override string GetLaunchArguments(GamePresetProperty property) { StringBuilder parameter = new(1024); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/Settings.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/Settings.cs index efd203467b..e1e59bf7fd 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/Settings.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/Settings.cs @@ -5,6 +5,8 @@ using System.Drawing; using System.Text; // ReSharper disable IdentifierTypo +// ReSharper disable GrammarMistakeInComment +// ReSharper disable StringLiteralTypo namespace CollapseLauncher.GameSettings.Genshin { diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs index 0f6ea47405..f18314a526 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs @@ -132,7 +132,7 @@ public static ScreenSettingData Load(IGameSettings gameSettings) $"{ex}", ex)); } - return new ScreenSettingData(); + return new ScreenSettingData(gameSettings); } public override void Save() diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs index f9ebebfaee..9718db6c2e 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs @@ -6,7 +6,8 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Text; +using System.Runtime.CompilerServices; + // ReSharper disable InconsistentNaming // ReSharper disable StringLiteralTypo @@ -77,15 +78,16 @@ public override void SaveSettings() public override string GetLaunchArguments(GamePresetProperty property) { - StringBuilder parameter = new(1024); + DefaultInterpolatedStringHandler builder = new(); if (SettingsCollapseScreen.UseCustomResolution) { Size screenSize = SettingsScreen.sizeRes; - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + builder.AppendFormatted(screenSize.Width, "-screen-width 0 "); + builder.AppendFormatted(screenSize.Height, "-screen-height 0 "); } - //Enable MobileMode + // Enable MobileMode if (SettingsCollapseMisc.LaunchMobileMode) { // Force save on every launch @@ -95,22 +97,22 @@ public override string GetLaunchArguments(GamePresetProperty property) if (SettingsCollapseScreen.GameGraphicsAPI == 4) { - parameter.Append("-use-d3d12 "); + builder.AppendLiteral("-use-d3d12 "); } if (SettingsCollapseScreen.UseBorderlessScreen) { - parameter.Append("-popupwindow "); + builder.AppendLiteral("-popupwindow "); } string customArgs = SettingsCustomArgument.CustomArgumentValue; if (SettingsCollapseMisc.UseCustomArguments && !string.IsNullOrEmpty(customArgs)) { - parameter.Append(customArgs); + builder.AppendLiteral(customArgs); } - return parameter.ToString(); + return builder.ToStringAndClear(); } public override IGameSettingsUniversal AsIGameSettingsUniversal() => this; diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs index 9c470f1bbe..dca17f19f6 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs @@ -9,6 +9,7 @@ using Hi3Helper; using Hi3Helper.CommunityToolkit.WinUI.Controls; using Hi3Helper.SentryHelper; +using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Shared.Region; using Hi3Helper.Win32.WinRT.WindowsStream; using Microsoft.UI.Text; @@ -82,7 +83,7 @@ public partial class ImageBackgroundManager Uri overlayImageUri = !string.IsNullOrEmpty(decodedOverlayPath) ? new Uri(decodedOverlayPath) : new Uri(Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\Images\ImageCropperOverlay", - LauncherConfig.GetAppConfigValue("WindowSizeProfile") == "Small" + LauncherConfig.GetAppConfigValue("WindowSizeProfile").ToEnum() == WindowSizeProfile.Small ? "small.png" : "normal.png")); diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index d736088107..9c2840714a 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -46,7 +46,17 @@ private void LoadImageAtIndex(int index, bool forceLoadToStatic, CancellationTok } IsBackgroundLoading = true; - new Thread(async () => await LoadImageAtIndexCore(index, forceLoadToStatic, token).ConfigureAwait(false)) + new Thread(async void () => + { + try + { + await LoadImageAtIndexCore(index, forceLoadToStatic, token).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogWriteLine($"{e}", LogType.Error, true); + } + }) { IsBackground = true, Priority = ThreadPriority.Lowest @@ -126,7 +136,17 @@ private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, Cance } // -- Read Color Accent information from current background context. - new Thread(async context => await GetMediaAccentColor(context).ConfigureAwait(false)) + new Thread(async void (ctx) => + { + try + { + await GetMediaAccentColor(ctx).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogWriteLine($"{e}", LogType.Error, true); + } + }) { IsBackground = true }.UnsafeStart((downloadedBackgroundUri, isUseFFmpeg)); @@ -255,18 +275,11 @@ private void SpawnImageLayer(Uri? overlayFilePath, converter: StaticConverter.Shared); layerElement.ImageLoaded += LayerElementOnLoaded; - layerElement.CanvasSizeChanged += LayerElementCanvasSizeChanged; PresenterGrid?.Children.Add(layerElement); layerElement.Tag = isVideo; } - private void LayerElementCanvasSizeChanged(LayeredBackgroundImage layerElement, Size size) - { - CurrentElementWidth = size.Width; - CurrentElementHeight = size.Height; - } - private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) { layerElement.ImageLoaded -= LayerElementOnLoaded; @@ -277,10 +290,6 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) foreach (UIElement element in elementToRemove) { PresenterGrid?.Children.Remove(element); - if (element is LayeredBackgroundImage asLayeredImage) - { - asLayeredImage.CanvasSizeChanged -= LayerElementCanvasSizeChanged; - } } if (CurrentIsEnableBackgroundAutoPlay && WindowUtility.CurrentWindowIsVisible) @@ -356,7 +365,7 @@ void Impl() FileAccess.Write, FileShare.ReadWrite); - pipeline.AddTransform(new Waifu2XTransform(ImageLoaderHelper._waifu2X)); + pipeline.AddTransform(new Waifu2XTransform(ImageLoaderHelper.Waifu2X)); pipeline.WriteOutput(outputFileStream); } } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs index 0213181534..9076ceff3b 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs @@ -5,7 +5,6 @@ using CollapseLauncher.Interfaces; using CollapseLauncher.Interfaces.Class; using CollapseLauncher.XAMLs.Theme.CustomControls; -using Google.Protobuf.WellKnownTypes; using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; @@ -303,25 +302,9 @@ private set } } - public double CurrentElementWidth - { - get; - set - { - field = value; - OnPropertyChanged(); - } - } + public double DesignWidth => 2560d; - public double CurrentElementHeight - { - get; - set - { - field = value; - OnPropertyChanged(); - } - } + public double DesignHeight => 1440d; /// /// The collection of image context sources.

diff --git a/CollapseLauncher/Classes/GamePresetProperty.cs b/CollapseLauncher/Classes/GamePresetProperty.cs index 82a02d7fb9..8b71b2b181 100644 --- a/CollapseLauncher/Classes/GamePresetProperty.cs +++ b/CollapseLauncher/Classes/GamePresetProperty.cs @@ -21,7 +21,6 @@ using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml; using System; -using System.Collections.Generic; using System.IO; // ReSharper disable UnusedMember.Global @@ -54,7 +53,7 @@ internal static GamePresetProperty Create(UIElement uiElementParent, ILauncherAp property.GameSettings = new HonkaiSettings(property.GameVersion); property.GameCache = new HonkaiCache(uiElementParent, property.GameVersion, property.GameSettings); property.GameRepair = new HonkaiRepairV2(uiElementParent, property.GameVersion, property.GameSettings); - property.GameInstall = new HonkaiInstall(uiElementParent, property.GameVersion, property.GameSettings); + property.GameInstall = new HonkaiInstall(uiElementParent, property.GameVersion, property.GameSettings, property.GameRepair); break; case GameNameType.StarRail: property.GameVersion = new GameTypeStarRailVersion(launcherApis, gamePreset); diff --git a/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs b/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs index 9f5c4c40ee..52765a5a4e 100644 --- a/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs +++ b/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs @@ -5,7 +5,6 @@ using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.Http; -using Hi3Helper.Shared.Region; using System; using System.Collections.Generic; using System.IO; diff --git a/CollapseLauncher/Classes/Helper/Image/ImageConverterHelper.cs b/CollapseLauncher/Classes/Helper/Image/ImageConverterHelper.cs index 14550b3665..41d4bef4e7 100644 --- a/CollapseLauncher/Classes/Helper/Image/ImageConverterHelper.cs +++ b/CollapseLauncher/Classes/Helper/Image/ImageConverterHelper.cs @@ -9,50 +9,48 @@ namespace CollapseLauncher.Classes.Helper.Image { internal static class ImageConverterHelper { - const int MaxIconSize = 256; + private const int MaxIconSize = 256; public static Icon ConvertToIcon(DImage image) { - using var bitmap = ResizeToIconSize(image); + using Bitmap bitmap = ResizeToIconSize(image); if (bitmap == null || bitmap.Width > MaxIconSize || bitmap.Height > MaxIconSize) throw new Exception("Failed to resize image for icon conversion."); - using (var stream = new MemoryStream()) - { - bitmap.Save(stream, ImageFormat.Png); - var bytes = stream.ToArray(); + using MemoryStream stream = new(); + bitmap.Save(stream, ImageFormat.Png); + byte[] bytes = stream.ToArray(); - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); + using MemoryStream ms = new(); + using BinaryWriter bw = new(ms); - // ICONDIR structure (6 bytes) - // 0-1 reserved (0), 2-3 type (1 for icons), 4-5 count - bw.Write((ushort)0); // Reserved - bw.Write((ushort)1); // Image type (1 = icon) - bw.Write((ushort)1); // Number of images + // ICONDIR structure (6 bytes) + // 0-1 reserved (0), 2-3 type (1 for icons), 4-5 count + bw.Write((ushort)0); // Reserved + bw.Write((ushort)1); // Image type (1 = icon) + bw.Write((ushort)1); // Number of images - // ICONDIRENTRY (16 bytes) - bw.Write((byte)bitmap.Width); // width - bw.Write((byte)bitmap.Height); // height - bw.Write((byte)0); // Color palette (0 = no palette) - bw.Write((byte)0); // Reserved - bw.Write((ushort)0); // Color planes - bw.Write((ushort)32); // Bits per pixel + // ICONDIRENTRY (16 bytes) + bw.Write((byte)bitmap.Width); // width + bw.Write((byte)bitmap.Height); // height + bw.Write((byte)0); // Color palette (0 = no palette) + bw.Write((byte)0); // Reserved + bw.Write((ushort)0); // Color planes + bw.Write((ushort)32); // Bits per pixel - uint bytesInRes = (uint)bytes.Length; - uint imageOffset = 6 + 16; + uint bytesInRes = (uint)bytes.Length; + const uint imageOffset = 6 + 16; - bw.Write(bytesInRes); - bw.Write(imageOffset); + bw.Write(bytesInRes); + bw.Write(imageOffset); - bw.Write(bytes); + bw.Write(bytes); - bw.Flush(); - ms.Position = 0; + bw.Flush(); + ms.Position = 0; - return new Icon(ms); - } + return new Icon(ms); } private static Bitmap ResizeToIconSize(DImage sourceImage) @@ -68,16 +66,14 @@ private static Bitmap ResizeToIconSize(DImage sourceImage) int scaledWidth = (int)Math.Round(sourceImage.Width * scaleFactor); int scaledHeight = (int)Math.Round(sourceImage.Height * scaleFactor); - var bitmap = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppArgb); + Bitmap bitmap = new(scaledWidth, scaledHeight, PixelFormat.Format32bppArgb); - using (var g = Graphics.FromImage(bitmap)) - { - g.Clear(Color.Transparent); - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.SmoothingMode = SmoothingMode.HighQuality; - g.DrawImage(sourceImage, 0, 0, scaledWidth, scaledHeight); - } + using Graphics g = Graphics.FromImage(bitmap); + g.Clear(Color.Transparent); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.SmoothingMode = SmoothingMode.HighQuality; + g.DrawImage(sourceImage, 0, 0, scaledWidth, scaledHeight); return bitmap; } diff --git a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs index da5130ab71..c839b16361 100644 --- a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs +++ b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs @@ -51,14 +51,14 @@ internal static class ImageLoaderHelper }; #region Waifu2X - public static Waifu2X _waifu2X; + public static Waifu2X Waifu2X; private static Waifu2XStatus _cachedStatus = Waifu2XStatus.NotInitialized; public static Waifu2XStatus Waifu2XStatus => _cachedStatus; public static bool IsWaifu2XEnabled { - get => GetAppConfigValue("EnableWaifu2X").ToBool() && IsWaifu2XUsable && _waifu2X != null; + get => GetAppConfigValue("EnableWaifu2X").ToBool() && IsWaifu2XUsable && Waifu2X != null; set { SetAndSaveConfigValue("EnableWaifu2X", value); @@ -95,13 +95,13 @@ private static Waifu2X CreateWaifu2X() public static void InitWaifu2X() { - _waifu2X ??= CreateWaifu2X(); + Waifu2X ??= CreateWaifu2X(); } public static void DestroyWaifu2X() { - _waifu2X?.Dispose(); - _waifu2X = null; + Waifu2X?.Dispose(); + Waifu2X = null; } #endregion diff --git a/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs b/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs index 0ba637d9c1..cb4cbf279b 100644 --- a/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs +++ b/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs @@ -26,41 +26,41 @@ internal static partial class Waifu2XPInvoke [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial IntPtr ncnn_get_gpu_name(int gpuId); + internal static partial nint ncnn_get_gpu_name(int gpuId); #endregion #region Waifu2X PInvokes [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial IntPtr waifu2x_create(int gpuId, [MarshalAs(UnmanagedType.Bool)] bool ttaMode, int numThreads); + internal static partial nint waifu2x_create(int gpuId, [MarshalAs(UnmanagedType.Bool)] bool ttaMode, int numThreads); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial void waifu2x_destroy(IntPtr context); + internal static partial void waifu2x_destroy(nint context); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static unsafe partial int waifu2x_load(IntPtr context, byte* param, byte* model); + internal static unsafe partial int waifu2x_load(nint context, byte* param, byte* model); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static unsafe partial int waifu2x_process(IntPtr context, int w, int h, int c, byte* inData, byte* outData); + internal static unsafe partial int waifu2x_process(nint context, int w, int h, int c, byte* inData, byte* outData); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static unsafe partial int waifu2x_process_cpu(IntPtr context, int w, int h, int c, byte* inData, byte* outData); + internal static unsafe partial int waifu2x_process_cpu(nint context, int w, int h, int c, byte* inData, byte* outData); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial void waifu2x_set_param(IntPtr context, Param param, int value); + internal static partial void waifu2x_set_param(nint context, Param param, int value); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial int waifu2x_get_param(IntPtr context, Param param); + internal static partial int waifu2x_get_param(nint context, Param param); [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - internal static partial Waifu2XStatus waifu2x_self_test(IntPtr context); + internal static partial Waifu2XStatus waifu2x_self_test(nint context); [LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] @@ -104,7 +104,7 @@ public enum Waifu2XStatus #endregion #region Properties - private IntPtr _context; + private nint _context; #endregion diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs index 9ae4bb817d..228113679b 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs @@ -12,7 +12,7 @@ internal static class CollapsePInvoke { private const string LibraryExtension = ".dll"; - internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + internal static nint DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) { bool retryFirst = false; string pathToLoad = libraryName; @@ -23,11 +23,11 @@ internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, return dllHandle; } - // Try append extension in case loading was failed due to it. Try to load it once again. + // Try to append extension in case loading was failed due to it. Try to load it once again. if (!pathToLoad.EndsWith(LibraryExtension, StringComparison.OrdinalIgnoreCase) && !retryFirst) { retryFirst = true; - pathToLoad = Path.Combine(Path.GetDirectoryName(libraryName), + pathToLoad = Path.Combine(Path.GetDirectoryName(libraryName) ?? "", Path.GetFileNameWithoutExtension(libraryName) + LibraryExtension); goto LoadFirst; } @@ -50,7 +50,7 @@ private static bool TryLoadFromDir(Assembly assembly, string dirPath, string lib string dllFileName = Path.GetFileName(libraryName); string dllPathOnDir = Path.Combine(dirPath, dllFileName); Load: - if (LoadInternal(dllPathOnDir, assembly, null) is var dllHandle && + if (LoadInternal(dllPathOnDir, assembly) is var dllHandle && dllHandle != nint.Zero) { handle = dllHandle; @@ -58,16 +58,17 @@ private static bool TryLoadFromDir(Assembly assembly, string dirPath, string lib } // If the second attempt by loading from specific dir also failed, - // Try append extension in case loading was failed due to it. Then try to load it once again :fingercrossed: - if (!dllPathOnDir.EndsWith(LibraryExtension, StringComparison.OrdinalIgnoreCase) && !retry) + // Try to append extension in case loading was failed due to it. Then try to load it once again :fingercrossed: + if (dllPathOnDir.EndsWith(LibraryExtension, StringComparison.OrdinalIgnoreCase) || retry) { - retry = true; - dllPathOnDir = Path.Combine(Path.GetDirectoryName(dllPathOnDir), - Path.GetFileNameWithoutExtension(dllPathOnDir) + LibraryExtension); - goto Load; + return false; } - return false; + retry = true; + dllPathOnDir = Path.Combine(Path.GetDirectoryName(dllPathOnDir) ?? "", + Path.GetFileNameWithoutExtension(dllPathOnDir) + LibraryExtension); + goto Load; + } private static nint LoadInternal(string path, Assembly assembly, DllImportSearchPath? searchPath = null) diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs index 23e6d891ca..d93e65ad9e 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs @@ -1,6 +1,9 @@ using System; using System.Runtime.InteropServices; using System.Text; +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming +#pragma warning disable IDE0130 #nullable enable namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; @@ -14,7 +17,7 @@ public unsafe struct AVCodec public AVCodecID Id; public int Capabilities; public byte MaxLowRes; - public AVRational* SupportedFramerates; + public AVRational* SupportedFrameRates; public AVPixelFormat* SupportedPixelFormats; public int* SupportedSampleRates; public AVSampleFormat* SupportedSampleFormats; @@ -30,6 +33,6 @@ public unsafe struct AVCodec private static string? GetUtf8StringFrom(byte* ptr) { ReadOnlySpan bytes = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr); - return Encoding.UTF8.GetString(bytes); + return bytes.IsEmpty ? null : Encoding.UTF8.GetString(bytes); } } diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs index daa582f78e..2a4a59e3a5 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs @@ -1,4 +1,10 @@ -namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +#pragma warning disable IDE0130 + +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; // https://ffmpeg.org/doxygen/7.0/group__lavc__core.html#gaadca229ad2c20e060a14fec08a5cc7ce public enum AVCodecID diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs index 396bcac7fa..37287110a5 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs @@ -1,4 +1,10 @@ -namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +#pragma warning disable IDE0130 + +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; public enum AVPixelFormat { diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs index e6548b292d..81ecbd5c13 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs @@ -1,5 +1,4 @@ -using System; -namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; public struct AVRational { diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs index b6ac7dcf65..fc29292d47 100644 --- a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs @@ -1,4 +1,10 @@ -namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +#pragma warning disable IDE0130 + +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; public enum AVSampleFormat { diff --git a/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs b/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs index f5252fecf9..4dab64e577 100644 --- a/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs +++ b/CollapseLauncher/Classes/Helper/JsonConverter/SlashToBackslashConverter.cs @@ -6,7 +6,7 @@ #nullable enable namespace CollapseLauncher.Helper.JsonConverter { - internal class SlashToBackslashConverter : JsonConverter + internal class SlashToBackslashConverter : JsonConverter { public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -17,9 +17,9 @@ internal class SlashToBackslashConverter : JsonConverter return ConverterTool.NormalizePath(str); } - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) { - string replaced = value.Replace('\\', '/'); + string? replaced = value?.Replace('\\', '/'); writer.WriteStringValue(replaced); } } diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypApiLoader.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypApiLoader.cs index 9c7405647a..bd41ef14e7 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypApiLoader.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypApiLoader.cs @@ -121,7 +121,7 @@ await sophonUrls.EnsureReassociated(ApiGeneralHttpClient, sophonBranchUrl, PresetConfig.LauncherBizName!, false, - token); + token).ConfigureAwait(false); sophonUrls.ResetAssociation(); // Reset association so it won't conflict with preload/update/install activity @@ -140,7 +140,7 @@ await launcherSophonBranchCallback ExecutionTimeoutAttempt, onTimeoutRoutine, result => LauncherGameSophonBranches = result, - token); + token).ConfigureAwait(false); } private void InitializeFakeVersionInfo() @@ -201,7 +201,7 @@ private static void AddFakeVersionInfo(HypGameInfoBranchData branchData, HypReso Version = branchData.Tag }; - HashSet existingDiffsVer = new HashSet(region.Patches.Select(x => x.Version.ToString())); + HashSet existingDiffsVer = [.. region.Patches.Select(x => x.Version.ToString())]; foreach (string versionTag in (branchData.DiffTags ?? []) .Where(x => !existingDiffsVer.Contains(x))) { diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherContentApi.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherContentApi.cs index b0076a205c..ffdb775fe9 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherContentApi.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherContentApi.cs @@ -1,7 +1,6 @@ using CollapseLauncher.Helper.JsonConverter; using CollapseLauncher.Interfaces.Class; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json.Serialization; using WinRT; @@ -28,50 +27,25 @@ public class HypLauncherContentKind : HypApiIdentifiable public List Carousel { get; init; } = []; [JsonPropertyName("posts")] - public List News { get; init; } = []; + public List News + { + get; + init; + } = []; [JsonPropertyName("social_media_list")] public List SocialMedia { get; init; } = []; // Extensions - [JsonIgnore] - [field: AllowNull, MaybeNull] - internal List NewsEventKind - { - get => - field ??= News - .Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY) - .ToList(); - private set; - } - - [JsonIgnore] - [field: AllowNull, MaybeNull] - internal List NewsAnnouncementKind - { - get => - field ??= News - .Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE) - .ToList(); - private set; - } - - [JsonIgnore] - [field: AllowNull, MaybeNull] - internal List NewsInformationKind - { - get => - field ??= News - .Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_INFO) - .ToList(); - private set; - } + [JsonIgnore] internal List NewsEventKind { get => field ??= News.Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY).ToList(); } + [JsonIgnore] internal List NewsAnnouncementKind { get => field ??= News.Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE).ToList(); } + [JsonIgnore] internal List NewsInformationKind { get => field ??= News.Where(x => x.ContentType == LauncherGameNewsPostType.POST_TYPE_INFO).ToList(); } public void ResetCachedNews() { - NewsEventKind = []; - NewsAnnouncementKind = []; - NewsInformationKind = []; + NewsEventKind.Clear(); + NewsAnnouncementKind.Clear(); + NewsInformationKind.Clear(); } } diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/ILauncherApi.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/ILauncherApi.cs index 2ae7900d5a..b18b6661ba 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/ILauncherApi.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/ILauncherApi.cs @@ -34,10 +34,10 @@ public interface ILauncherApi : IDisposable HttpClient? ApiGeneralHttpClient { get; } HttpClient? ApiResourceHttpClient { get; } bool IsPlugin { get; } - ValueTask LoadAsync(Func? beforeLoadRoutineAsync = null, - Func? afterLoadRoutineAsync = null, - ActionOnTimeOutRetry? onTimeoutRoutine = null, - Action? errorLoadRoutine = null, - CancellationToken token = default); + Task LoadAsync(Func? beforeLoadRoutineAsync = null, + Func? afterLoadRoutineAsync = null, + ActionOnTimeOutRetry? onTimeoutRoutine = null, + Action? errorLoadRoutine = null, + CancellationToken token = default); } } diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs index 4ecb99f76c..49d63e3d1a 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs @@ -3,9 +3,9 @@ using CollapseLauncher.Helper.Metadata; using Hi3Helper.SentryHelper; using System; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using System.Net.Http; // ReSharper disable PartialTypeWithSinglePart // ReSharper disable IdentifierTypo // ReSharper disable StringLiteralTypo @@ -61,19 +61,19 @@ public void Dispose() GC.SuppressFinalize(this); } - public async ValueTask LoadAsync(Func? beforeLoadRoutineAsync, - Func? afterLoadRoutineAsync, - ActionOnTimeOutRetry? onTimeoutRoutine, - Action? errorLoadRoutine, - CancellationToken token) + public async Task LoadAsync(Func? beforeLoadRoutineAsync, + Func? afterLoadRoutineAsync, + ActionOnTimeOutRetry? onTimeoutRoutine, + Action? errorLoadRoutine, + CancellationToken token) { try { IsLoadingCompleted = false; - await (beforeLoadRoutineAsync?.Invoke(token) ?? ValueTask.CompletedTask); + await (beforeLoadRoutineAsync?.Invoke(token) ?? Task.CompletedTask); - await LoadAsyncInner(onTimeoutRoutine, token); - await (afterLoadRoutineAsync?.Invoke(token) ?? ValueTask.CompletedTask); + await LoadAsyncInner(onTimeoutRoutine, token).ConfigureAwait(false); + await (afterLoadRoutineAsync?.Invoke(token) ?? Task.CompletedTask); return true; } diff --git a/CollapseLauncher/Classes/Helper/Locale.cs b/CollapseLauncher/Classes/Helper/Locale.cs index ed61bd481f..6e990c9882 100644 --- a/CollapseLauncher/Classes/Helper/Locale.cs +++ b/CollapseLauncher/Classes/Helper/Locale.cs @@ -324,7 +324,7 @@ private static void DownloadFallbackIfNotPresent(string localePath) try { string? dir = LauncherConfig.AppLangFolder; - for (int i = 0; i < 10 && dir != null; i++) + for (int i = 0; i < 10; i++) { dir = Path.GetDirectoryName(dir); if (dir == null) break; @@ -362,7 +362,7 @@ public void EnableLocaleHotReload() if (_langFileWatcher != null) return; _sourceLangFolder = FindSourceLangFolder(); - string watchFolder = _sourceLangFolder ?? LauncherConfig.AppLangFolder!; + string watchFolder = _sourceLangFolder ?? LauncherConfig.AppLangFolder; _langFileWatcher = new FileSystemWatcher(watchFolder, "*.json") { @@ -402,18 +402,19 @@ public void EnableLocaleHotReload() } }, null, Timeout.Infinite, Timeout.Infinite); - void OnFileEvent(object sender, FileSystemEventArgs e) + _langFileWatcher.Changed += OnFileEvent; + _langFileWatcher.Created += OnFileEvent; + _langFileWatcher.Renamed += OnFileEvent; + + Logger.LogWriteLine($"[Locale::HotReload] Watching: {watchFolder}", LogType.Scheme, true); + return; + + void OnFileEvent(object? sender, FileSystemEventArgs e) { if (!e.FullPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) return; _pendingChangedFile = e.FullPath; _langReloadDebounce!.Change(300, Timeout.Infinite); } - - _langFileWatcher.Changed += OnFileEvent; - _langFileWatcher.Created += OnFileEvent; - _langFileWatcher.Renamed += (_, e) => OnFileEvent(_, e); - - Logger.LogWriteLine($"[Locale::HotReload] Watching: {watchFolder}", LogType.Scheme, true); } /// diff --git a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs index 9dad84b017..1d3cad83f6 100644 --- a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs +++ b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs @@ -2,6 +2,7 @@ using CollapseLauncher.Helper.JsonConverter; using CollapseLauncher.Helper.LauncherApiLoader; using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; +using CollapseLauncher.Interfaces.Class; using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool; @@ -241,32 +242,31 @@ internal static class PresetConfigExt string launcherIdOrPasswordHead, string gameIdOrGamePackageIdHead, string? launcherIdOrPassword, string? gameIdOrGamePackageId) { - if (string.IsNullOrEmpty(url)) + if (string.IsNullOrEmpty(url) + || string.IsNullOrEmpty(launcherIdOrPassword) + || string.IsNullOrEmpty(gameIdOrGamePackageId)) return url; - if (string.IsNullOrEmpty(launcherIdOrPassword) || string.IsNullOrEmpty(gameIdOrGamePackageId)) - return url; - - int urlLen = url.Length + (1 << 10); - char[] urlBuffer = ArrayPool.Shared.Rent(urlLen); - Span urlSpanBuffer = urlBuffer; - ReadOnlySpan urlSpan = url; + int urlLen = url.Length + (1 << 10); + char[] urlBuffer = ArrayPool.Shared.Rent(urlLen); + Span urlSpanBuffer = urlBuffer; + ReadOnlySpan urlSpan = url; try { - int urlSpanBufferLen = 0; - Span splitRanges = stackalloc Range[32]; - int urlSplitRangesLen = urlSpan.Split(splitRanges, '?', StringSplitOptions.RemoveEmptyEntries); + int urlSpanBufferLen = 0; + Span splitRanges = stackalloc Range[32]; + int urlSplitRangesLen = urlSpan.Split(splitRanges, '?', StringSplitOptions.RemoveEmptyEntries); if (urlSplitRangesLen < 2) return url; ReadOnlySpan urlPathSpan = urlSpan[splitRanges[0]]; - ReadOnlySpan querySpan = urlSpan[splitRanges[1]]; + ReadOnlySpan querySpan = urlSpan[splitRanges[1]]; if (!urlPathSpan.TryCopyTo(urlSpanBuffer)) throw new InvalidOperationException("Failed to copy url path string to buffer"); - urlSpanBufferLen += splitRanges[0].End.Value - splitRanges[0].Start.Value; - urlSpanBuffer[urlSpanBufferLen++] = '?'; + urlSpanBufferLen += splitRanges[0].End.Value - splitRanges[0].Start.Value; + urlSpanBuffer[urlSpanBufferLen++] = '?'; #region Parse and split queries - Sanitize the GameId and GameId query int querySplitRangesLen = querySpan.Split(splitRanges, '&', StringSplitOptions.RemoveEmptyEntries); @@ -274,13 +274,13 @@ internal static class PresetConfigExt Span querySpanBuffer = urlSpanBuffer[urlSpanBufferLen..]; for (int i = querySplitRangesLen - 1; i > -1; i--) { - Range segmentRange = splitRanges[i]; - int segmentLen = segmentRange.End.Value - segmentRange.Start.Value; + Range segmentRange = splitRanges[i]; + int segmentLen = segmentRange.End.Value - segmentRange.Start.Value; ReadOnlySpan querySegment = querySpan[segmentRange]; // Skip GameId or GameId query head - if (querySegment.StartsWith(launcherIdOrPasswordHead, StringComparison.OrdinalIgnoreCase) - || querySegment.StartsWith(gameIdOrGamePackageIdHead, StringComparison.OrdinalIgnoreCase)) + if (querySegment.StartsWith(launcherIdOrPasswordHead, StringComparison.OrdinalIgnoreCase) + || querySegment.StartsWith(gameIdOrGamePackageIdHead, StringComparison.OrdinalIgnoreCase)) continue; // Otherwise, add others @@ -294,7 +294,7 @@ internal static class PresetConfigExt // Append '&' if (queryWritten > 0 && querySpanBuffer[queryWritten - 1] != '&' - && querySpanBuffer[queryWritten - 1] != '?') + && querySpanBuffer[queryWritten - 1] != '?') querySpanBuffer[queryWritten++] = '&'; urlSpanBufferLen += queryWritten; @@ -303,8 +303,8 @@ internal static class PresetConfigExt #region Append GameId and GameId query if (!launcherIdOrPasswordHead.TryCopyTo(urlSpanBuffer[urlSpanBufferLen..])) throw new InvalidOperationException("Failed to copy launcher id or password head string to buffer"); - urlSpanBufferLen += launcherIdOrPasswordHead.Length; - urlSpanBuffer[urlSpanBufferLen++] = '='; + urlSpanBufferLen += launcherIdOrPasswordHead.Length; + urlSpanBuffer[urlSpanBufferLen++] = '='; if (!launcherIdOrPassword.TryCopyTo(urlSpanBuffer[urlSpanBufferLen..])) throw new InvalidOperationException("Failed to copy launcher id or password value string to buffer"); urlSpanBufferLen += launcherIdOrPassword.Length; @@ -313,13 +313,13 @@ internal static class PresetConfigExt if (!gameIdOrGamePackageIdHead.TryCopyTo(urlSpanBuffer[urlSpanBufferLen..])) throw new InvalidOperationException("Failed to copy game id or package id head string to buffer"); - urlSpanBufferLen += gameIdOrGamePackageIdHead.Length; - urlSpanBuffer[urlSpanBufferLen++] = '='; + urlSpanBufferLen += gameIdOrGamePackageIdHead.Length; + urlSpanBuffer[urlSpanBufferLen++] = '='; if (!gameIdOrGamePackageId.TryCopyTo(urlSpanBuffer[urlSpanBufferLen..])) throw new InvalidOperationException("Failed to copy game id or package id value string to buffer"); urlSpanBufferLen += gameIdOrGamePackageId.Length; - string returnString = new string(urlBuffer, 0, urlSpanBufferLen); + string returnString = new(urlBuffer, 0, urlSpanBufferLen); #if DEBUG LogWriteLine($"URL string's GameId or Password and GameId or PackageId association has been successfully replaced!\r\nSource: {url}\r\nResult: {returnString}", LogType.Debug, true); #endif @@ -337,7 +337,8 @@ internal static class PresetConfigExt [JsonSerializable(typeof(PresetConfig))] internal sealed partial class PresetConfigJsonContext : JsonSerializerContext; - public class PresetConfig + [GeneratedBindableCustomProperty] + public partial class PresetConfig : NotifyPropertyChanged { #region Constants // ReSharper disable once UnusedMember.Local @@ -795,12 +796,8 @@ private void SetVoiceLanguageID_StarRail(int langID) if (!value.DataVersion.TryGetValue(verInt, out GameDataVersion? verData)) { // Fallback to find the default value anyway - KeyValuePair? kvpTemp = value.DataVersion.FirstOrDefault(); - if (kvpTemp == null) - return null; - - // ReSharper disable once ConstantConditionalAccessQualifier - verData = kvpTemp?.Value; + KeyValuePair kvpTemp = value.DataVersion.FirstOrDefault(); + verData = kvpTemp.Value; } if (verData == null) diff --git a/CollapseLauncher/Classes/Helper/SpeedLimiterService.cs b/CollapseLauncher/Classes/Helper/SpeedLimiterService.cs index 5c54356d7c..a3dc762459 100644 --- a/CollapseLauncher/Classes/Helper/SpeedLimiterService.cs +++ b/CollapseLauncher/Classes/Helper/SpeedLimiterService.cs @@ -236,7 +236,7 @@ public static unsafe ValueTask AddBytesOrWaitAsync( } [StructLayout(LayoutKind.Sequential, Pack = 8)] // Pack to 8 bytes to ensure aligning - private struct ThrottleServiceContext + private ref struct ThrottleServiceContext { public long AvailableTokens; public long LastTimestamp; diff --git a/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs index 55f00c8f8a..188feb099b 100644 --- a/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs +++ b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs @@ -174,7 +174,7 @@ private static void CreateShortcut( Directory.CreateDirectory(iconLocationDir); // Create ShellLink instance - using ShellLink shellLink = new ShellLink(); + ShellLink shellLink = new(); // If existing icon exist, try open it try diff --git a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs index 61cc73e1a1..9ddef31b72 100644 --- a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs +++ b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs @@ -5,9 +5,9 @@ using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using System; -using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading.Tasks; using Velopack; using Velopack.Locators; using Velopack.Logging; @@ -60,7 +60,8 @@ internal static async Task IsUpdateAvailable(bool isForceCheckUpdate = fal IFileDownloader updateManagerHttpAdapter = new UpdateManagerHttpAdapter(); // Initialize update manager logger, locator and options IVelopackLogger? velopackLogger = ILoggerHelper.GetILogger("Velopack").ToVelopackLogger(); - IVelopackLocator updateManagerLocator = VelopackLocator.CreateDefaultForPlatform(velopackLogger); + DefaultProcessImpl defaultProcessImpl = new(velopackLogger); + IVelopackLocator updateManagerLocator = VelopackLocator.CreateDefaultForPlatform(defaultProcessImpl, velopackLogger); UpdateOptions updateManagerOptions = new() { AllowVersionDowngrade = true, diff --git a/CollapseLauncher/Classes/Helper/WindowUtility.cs b/CollapseLauncher/Classes/Helper/WindowUtility.cs index c9e1535f7f..9e98f63149 100644 --- a/CollapseLauncher/Classes/Helper/WindowUtility.cs +++ b/CollapseLauncher/Classes/Helper/WindowUtility.cs @@ -52,7 +52,7 @@ internal enum WindowBackdropKind None } - internal delegate IntPtr WndProcDelegate(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr lParam); + internal delegate nint WndProcDelegate(nint hwnd, uint msg, nuint wParam, nint lParam); internal static class WindowUtility { private static event EventHandler? DragAreaChangeEvent; @@ -478,15 +478,15 @@ private static void MainWinEventHook(nint hook, #region WndProc Handler - private static IntPtr InstallWndProcCallback(IntPtr hwnd, WndProcDelegate wndProc) + private static nint InstallWndProcCallback(nint hwnd, WndProcDelegate wndProc) { // Install WndProc hook const int GWLP_WNDPROC = -4; - IntPtr pWndProc = Marshal.GetFunctionPointerForDelegate(wndProc); + nint pWndProc = Marshal.GetFunctionPointerForDelegate(wndProc); return PInvoke.SetWindowLongPtr(hwnd, GWLP_WNDPROC, pWndProc); } - private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr lParam) + private static nint MainWndProc(nint hwnd, uint msg, nuint wParam, nint lParam) { const uint WM_SYSCOMMAND = 0x0112; const uint WM_SHOWWINDOW = 0x0018; @@ -585,7 +585,7 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr const int HTTOPRIGHT = 14; const int HTCLOSE = 20; - IntPtr result = PInvoke.CallWindowProc(_oldMainWndProcPtr, hwnd, msg, wParam, lParam); + nint result = PInvoke.CallWindowProc(_oldMainWndProcPtr, hwnd, msg, wParam, lParam); return result switch { // Hide all system buttons @@ -631,7 +631,7 @@ private static IntPtr MainWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr case WM_ENDSESSION: // wParam is TRUE if session is ending - if (wParam != UIntPtr.Zero) + if (wParam != nuint.Zero) { if (MainWindow.IsCriticalOpInProgress) { @@ -745,11 +745,11 @@ private static void ApplyWindowBorderFix() PInvoke.SetWindowPos(CurrentWindowPtr, 0, 0, 0, 0, 0, flags); } - IntPtr desktopSiteBridgeHwnd = PInvoke.FindWindowEx(CurrentWindowPtr, 0, "Microsoft.UI.Content.DesktopChildSiteBridge", ""); + nint desktopSiteBridgeHwnd = PInvoke.FindWindowEx(CurrentWindowPtr, 0, "Microsoft.UI.Content.DesktopChildSiteBridge", ""); _oldDesktopSiteBridgeWndProcPtr = InstallWndProcCallback(desktopSiteBridgeHwnd, DesktopSiteBridgeWndProc); } - private static IntPtr DesktopSiteBridgeWndProc(IntPtr hwnd, uint msg, UIntPtr wParam, IntPtr lParam) + private static nint DesktopSiteBridgeWndProc(nint hwnd, uint msg, nuint wParam, nint lParam) { const uint WM_WINDOWPOSCHANGING = 0x0046; @@ -868,7 +868,7 @@ internal static void DisableWindowNonClientArea() internal static bool IsCurrentWindowInFocus() { - IntPtr currentForegroundWindow = PInvoke.GetForegroundWindow(); + nint currentForegroundWindow = PInvoke.GetForegroundWindow(); return CurrentWindowPtr == currentForegroundWindow; } #endregion @@ -934,7 +934,7 @@ public static void ToggleToTray_AllWindow() public static void Tray_ShowNotification(string title, string message, NotificationIcon icon = NotificationIcon.None, - IntPtr? customIconHandle = null, + nint? customIconHandle = null, bool largeIcon = false, bool sound = true, bool respectQuietTime = true, @@ -961,7 +961,7 @@ private static void Service_ToastNotificationCallback(string app, string arg, Di // to foreground. window._TrayIcon?.ToggleMainVisibility(true); - IntPtr consoleWindowHandle = PInvoke.GetConsoleWindow(); + nint consoleWindowHandle = PInvoke.GetConsoleWindow(); if (LauncherConfig.GetAppConfigValue("EnableConsole") && PInvoke.IsWindowVisible(consoleWindowHandle)) { window._TrayIcon?.ToggleConsoleVisibility(true); diff --git a/CollapseLauncher/Classes/InstallManagement/Base/HDiffMap.cs b/CollapseLauncher/Classes/InstallManagement/Base/HDiffMap.cs index 7229b40131..581e879883 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/HDiffMap.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/HDiffMap.cs @@ -38,7 +38,7 @@ internal class HDiffMapEntry [JsonPropertyName("source_file_name")] [JsonConverter(typeof(NormalizedPathStringConverter))] - public string? SourceFileName { get; set; } + public required string SourceFileName { get; set; } [JsonPropertyName("source_file_size")] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs index 873039be1d..d682755989 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs @@ -90,7 +90,7 @@ public virtual bool IsUseSophon { get { - if ((_canDeltaPatch && !_forceIgnoreDeltaPatch) || GameVersionManager?.GamePreset.LauncherResourceChunksURL == null) + if ((_canDeltaPatch && !_forceIgnoreDeltaPatch) || GameVersionManager.GamePreset.LauncherResourceChunksURL == null) { return false; } @@ -188,7 +188,7 @@ protected virtual async Task StartPackageInstallSophon(GameInstallStateEnum game } // Get the requested URL and version based on current state. - if (GameVersionManager? + if (GameVersionManager .GamePreset .LauncherResourceChunksURL != null) { @@ -577,7 +577,7 @@ private async Task ConfirmAdditionalInstallDataPackageFiles( .Where(x => !string.IsNullOrEmpty(x.MatchingField)) .Select(x => x.MatchingField!).ToList(); - installManifest.AddRange(additionalMatchingFields.Select(matchingField => installManifestFirst.GetOtherManifestInfoPair(matchingField))); + installManifest.AddRange(additionalMatchingFields.Select(installManifestFirst.GetOtherManifestInfoPair)); return; string GetFileDetails() @@ -903,6 +903,11 @@ await Parallel.ForEachAsync(sophonUpdateAssetList parallelOptions, (asset, _) => Action(httpClient, asset)); + // Forcely update status + UpdateSophonDownloadStatus(null!); + UpdateSophonFileTotalProgress(0, true); + UpdateSophonFileDownloadProgress(0, 0); + _isSophonPreloadCompleted = isPreloadMode; // If it's in update mode, then clean up the temp sophon verified files @@ -1191,6 +1196,11 @@ private async Task AddSophonAdditionalVODiffAssetsToList( string[] excludeMatchingFieldsPattern, SophonDownloadSpeedLimiter downloadSpeedLimiter) { + if (_gameAudioLangListPath == null) + { + return; + } + // Get the main VO language name from Id string mainLangId = GetLanguageLocaleCodeByID(_gameVoiceLanguageID); diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs index 1004d8f8c2..7df8215dc0 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs @@ -392,13 +392,13 @@ private async Task SpawnAdditionalPackageDownloadDialog(long baseDownloadS // Initialize the return list and iterate the manifests List patchAssets = []; - foreach (var manifestPair in patchManifestList) + foreach ((SophonChunkManifestInfoPair patch, SophonChunkManifestInfoPair main, _) in patchManifestList) { // Get the asset and add it to the list await foreach (SophonPatchAsset patchAsset in SophonPatch .EnumerateUpdateAsync(httpClient, - manifestPair.Patch, - manifestPair.Main, + patch, + main, updateVersionfrom, downloadLimiter, token)) @@ -409,13 +409,13 @@ private async Task SpawnAdditionalPackageDownloadDialog(long baseDownloadS // Find the removable assets and compare with the added list. List removableAssets = []; - foreach (var manifestPair in patchManifestList) + foreach ((SophonChunkManifestInfoPair patch, SophonChunkManifestInfoPair main, _) in patchManifestList) { // Get the asset and add it to the list await foreach (SophonPatchAsset patchAsset in SophonPatch .EnumerateRemovableAsync(httpClient, - manifestPair.Patch, - manifestPair.Main, + patch, + main, updateVersionfrom, patchAssets, token)) @@ -432,9 +432,13 @@ private async Task SpawnAdditionalPackageDownloadDialog(long baseDownloadS protected virtual async Task> GetAlterSophonPatchVOMatchingFields(CancellationToken token) { - FileInfo voAudioLangFileInfo = new(_gameAudioLangListPathStatic); List voAudioMatchingFields = []; + if (_gameAudioLangListPathStatic == null) + { + return voAudioMatchingFields; + } + FileInfo voAudioLangFileInfo = new(_gameAudioLangListPathStatic); if (!voAudioLangFileInfo.Exists) { return voAudioMatchingFields; @@ -534,6 +538,11 @@ await EnsureDiskSpaceSufficiencyAsync(downloadSizePatchOnlyRemote, // Run parallel pipeline for download await Parallel.ForEachAsync(pipelineDownloadEnumerable, parallelOptions, ImplDownload); + // Forcely update status + UpdateCurrentDownloadStatus(); + UpdateSophonFileTotalProgress(0, true); + UpdateSophonFileDownloadProgress(0, 0); + // If it's on preload mode, then return as we only need to perform patch download. if (isPreloadMode) { @@ -552,6 +561,11 @@ await EnsureDiskSpaceSufficiencyAsync(downloadSizePatchOnlyRemote, // Run parallel pipeline for patch await Parallel.ForEachAsync(pipelinePatchEnumerable, parallelOptions, ImplPatchUpdate); + // Forcely update status + UpdateCurrentDownloadStatus(); + UpdateSophonFileTotalProgress(0, true); + UpdateSophonFileDownloadProgress(0, 0); + if (_canDeleteZip) { patchAssets.RemovePatches(patchOutputDir); diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs index e70206506f..6e3f243756 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs @@ -114,8 +114,8 @@ internal partial class InstallManagerBase : ProgressBase, IG $"{Path.GetFileNameWithoutExtension(GameVersionManager.GamePreset.GameExecutableName)}_Data"); protected virtual string _gameDataPersistentPath => Path.Combine(_gameDataPath, "Persistent"); - protected virtual string _gameAudioLangListPath => null!; - protected virtual string _gameAudioLangListPathStatic => null!; + protected virtual string? _gameAudioLangListPath => null!; + protected virtual string? _gameAudioLangListPathStatic => null!; protected IRepair? _gameRepairTool { get; set; } protected bool _canDeleteHdiffReference => !File.Exists(Path.Combine(GamePath, "@NoDeleteHdiffReference")); protected bool _canDeleteZip => !File.Exists(Path.Combine(GamePath, "@NoDeleteZip")); @@ -615,7 +615,7 @@ public virtual async Task StartPackageDownload(bool skipDialog) // or the verification can't be started because the download never being performed first // 1 -> Continue to the next step (all passes) // -1 -> Cancel the operation - public virtual async ValueTask StartPackageVerification(List gamePackage) + public virtual async ValueTask StartPackageVerification(List? gamePackage) { // Skip routine if sophon is use GameInstallStateEnum gameState = await GameVersionManager.GetGameState(); @@ -712,7 +712,7 @@ private async ValueTask RunPackageVerificationRoutine(GameInstallPackage as case ContentDialogResult.Secondary: // To proceed on extracting the file even it's corrupted string fileName = Path.GetFileName(asset.PathOutput); ContentDialogResult installCorruptDialogResult = - await Dialog_GameInstallCorruptedDataAnyway(fileName ?? "", asset.Size); + await Dialog_GameInstallCorruptedDataAnyway(fileName, asset.Size); // If cancel is pressed, then cancel the whole process if (installCorruptDialogResult == ContentDialogResult.None) { @@ -736,7 +736,7 @@ public async Task StartPackageInstallation() await StartPackageInstallationInner(); } - protected virtual async Task StartPackageInstallationInner(List gamePackage = null, + protected virtual async Task StartPackageInstallationInner(List? gamePackage = null, bool isOnlyInstallPackage = false, bool doNotDeleteZipExplicit = false) { @@ -754,7 +754,7 @@ protected virtual async Task StartPackageInstallationInner(List 1; // Try to unassign read-only and redundant diff files @@ -813,7 +813,7 @@ protected virtual async Task StartPackageInstallationInner(List GetSingleOrSegmentedDownloadStream(asset), GamePath, - Token.Token); + Token!.Token); // Get the information about diff and delete list file FileInfo hdiffMapList = new FileInfo(Path.Combine(GamePath, "hdiffmap.json")) @@ -1422,7 +1422,7 @@ protected virtual async Task> GetHDiffMapEntryList(string ga while (await deletedFileMapReader.ReadLineAsync() is { } line) { string normalizedPath = line.NormalizePath(); - if (sourcePathsOnMap.TryGetValue(normalizedPath, out HDiffMapEntry valueEntry)) + if (sourcePathsOnMap.TryGetValue(normalizedPath, out HDiffMapEntry? valueEntry)) { valueEntry.CanDeleteSource = true; } @@ -1469,12 +1469,12 @@ protected virtual async Task ApplyHDiffMap() bool isSuccess = false; - FileInfo sourcePath = new FileInfo(GetBasePersistentDirectory(gameDir, entry.SourceFileName ?? "")) + FileInfo sourcePath = new FileInfo(GetBasePersistentDirectory(gameDir, entry.SourceFileName)) .StripAlternateDataStream().EnsureNoReadOnly(out bool isSourceExist); - string sourcePathDir = sourcePath.DirectoryName; + string sourcePathDir = sourcePath.DirectoryName ?? ""; FileInfo patchPath = new FileInfo(Path.Combine(gameDir, entry.PatchFileName ?? "")) .StripAlternateDataStream().EnsureNoReadOnly(out bool isPatchExist); - string targetPathBasedOnSource = Path.Combine(sourcePathDir ?? "", Path.GetFileName(entry.TargetFileName ?? "")); + string targetPathBasedOnSource = Path.Combine(sourcePathDir, Path.GetFileName(entry.TargetFileName ?? "")); FileInfo targetPath = new FileInfo(targetPathBasedOnSource) .EnsureCreationOfDirectory() .StripAlternateDataStream() @@ -1526,7 +1526,7 @@ protected virtual async Task ApplyHDiffMap() await Task.Factory.StartNew(state => { - CancellationToken thisInnerCtx = (CancellationToken)state; + CancellationToken thisInnerCtx = (CancellationToken)(state ?? CancellationToken.None); try { thisInnerCtx.ThrowIfCancellationRequested(); @@ -1726,7 +1726,7 @@ public virtual async Task ApplyHdiffListPatch() } } - private void EventListener_PatchEvent(object sender, PatchEvent e) + private void EventListener_PatchEvent(object? sender, PatchEvent e) { Interlocked.Add(ref ProgressAllSizeCurrent, e.Read); double speed = CalculateSpeed(e.Read); @@ -1746,7 +1746,7 @@ private void EventListener_PatchEvent(object sender, PatchEvent e) UpdateProgress(); } - private void EventListener_PatchLogEvent(object sender, LoggerEvent e) + private void EventListener_PatchLogEvent(object? sender, LoggerEvent e) { if (HDiffPatch.LogVerbosity == Verbosity.Quiet || (HDiffPatch.LogVerbosity == Verbosity.Debug @@ -1793,7 +1793,7 @@ public virtual List TryGetHDiffList() }); while (listReader.ReadLine() is {} currentLine) { - var prop = currentLine?.Deserialize(CoreLibraryJsonContext.Default.PkgVersionProperties); + PkgVersionProperties? prop = currentLine.Deserialize(CoreLibraryJsonContext.Default.PkgVersionProperties); if (prop == null) { @@ -2155,7 +2155,7 @@ private async ValueTask CheckExistingOfficialInstallation(bool isHasOnlyMig // If the "Use current directory" option is chosen (migrationOptionReturn == 1), then proceed to another routine. // If not, then return the migrationOptionReturn value. int migrationOptionReturn = - await PerformMigrationOption(GameVersionManager.GamePreset.ActualGameDataLocation, + await PerformMigrationOption(GameVersionManager.GamePreset.ActualGameDataLocation ?? "", MigrateFromLauncherType.Official, false, isHasOnlyMigrateOption); @@ -2231,7 +2231,7 @@ private async ValueTask CheckExistingSteamInstallation(bool isHasOnlyMigrat private async Task StartSteamMigration() { // Get game repair instance and if it's null, then return; - string? latestGameVersionString = GameVersionManager?.GetGameVersionApi()?.VersionString; + string? latestGameVersionString = GameVersionManager.GetGameVersionApi()?.VersionString; if (string.IsNullOrEmpty(latestGameVersionString)) return; @@ -2344,7 +2344,7 @@ private async ValueTask PerformMigrationOption(string path bool isMoveOperation = false, bool isHasOnlyMigrateOption = false) { - string launcherName = launcherType switch + string? launcherName = launcherType switch { MigrateFromLauncherType.Official => Locale.Current.Lang?._Misc?.LauncherNameOfficial, MigrateFromLauncherType.Steam => Locale.Current.Lang?._Misc?.LauncherNameSteam, @@ -2451,7 +2451,7 @@ private bool TryGetExistingSteamPath(ref string OutputPath) private bool TryGetExistingBHI3LPath(ref string OutputPath) { // If the preset doesn't have BetterHi3Launcher registry ver info, then return false - if (GameVersionManager?.GamePreset.BetterHi3LauncherVerInfoReg == null) + if (GameVersionManager.GamePreset.BetterHi3LauncherVerInfoReg == null) { return false; } @@ -2499,7 +2499,7 @@ private bool TryGetExistingBHI3LPath(ref string OutputPath) } - private async Task AskGameFolderDialog(Func>? checkExistingGameDelegate = null) + private async Task AskGameFolderDialog(Func>? checkExistingGameDelegate = null) { // Set initial folder variable as empty string folder = ""; @@ -2577,7 +2577,7 @@ protected virtual void TryAddPluginPackage(List assetList) const string pluginKeyEnd = "_version"; // Get the plugin resource list and if it's empty, return - List pluginResourceList = GameVersionManager?.GetGamePluginZip() ?? []; + List pluginResourceList = GameVersionManager.GetGamePluginZip() ?? []; if (pluginResourceList.Count == 0) { return; @@ -2634,7 +2634,7 @@ protected virtual void TryAddPluginPackage(List assetList) private async ValueTask CheckExistingOrAskFolderDialog() { // Try run the result and if it's null, then return -1 (Cancel the operation) - string result = await AskGameFolderDialog(GameVersionManager.FindGameInstallationPath); + string? result = await AskGameFolderDialog(GameVersionManager.FindGameInstallationPath); if (result == null) { return -1; @@ -2642,7 +2642,7 @@ private async ValueTask CheckExistingOrAskFolderDialog() // Check for existing installation and if it's found, then override result // with pathPossibleExisting value and return 0 to skip the process - string pathPossibleExisting = await GameVersionManager.FindGameInstallationPath(result); + string? pathPossibleExisting = await GameVersionManager.FindGameInstallationPath(result); if (pathPossibleExisting != null) { GameVersionManager.UpdateGamePath(pathPossibleExisting, false); @@ -2698,7 +2698,7 @@ protected virtual async ValueTask AddVoiceOverResourceVersionList( // Try find the VO resource by locale code if (TryGetVoiceOverResourceByLocaleCode(packageDetail.AudioPackage, localeCode, - out HypPackageData voRes)) + out HypPackageData? voRes)) { package = new GameInstallPackage(voRes, GamePath, packageDetail.UncompressedUrl, packageDetail.Version) { @@ -2716,7 +2716,7 @@ protected virtual async ValueTask AddVoiceOverResourceVersionList( else { // Get the dialog and go for the selection - (HashSet addedVO, string setAsDefaultVOLocalecode) = + (HashSet? addedVO, string? setAsDefaultVOLocalecode) = await Dialog_ChooseAudioLanguageChoice(langStringsDict); if (addedVO == null && string.IsNullOrEmpty(setAsDefaultVOLocalecode)) { @@ -2736,7 +2736,7 @@ protected virtual async ValueTask AddVoiceOverResourceVersionList( // Try find the VO resource by locale code if (!TryGetVoiceOverResourceByLocaleCode(packageDetail.AudioPackage, VoLocaleId, - out HypPackageData voRes)) + out HypPackageData? voRes)) { continue; } @@ -2757,30 +2757,39 @@ protected virtual async ValueTask AddVoiceOverResourceVersionList( } } - protected virtual async ValueTask AddMainResourceVersionList( + protected virtual ValueTask AddMainResourceVersionList( GamePackageResult packageResult, List packageList, bool isSkipMainPackage = false) { - // If the main package is not skipped, then add it. - // Otherwise, ignore it. - if (isSkipMainPackage) + try { - return; - } + // If the main package is not skipped, then add it. + // Otherwise, ignore it. + if (isSkipMainPackage) + { + return ValueTask.CompletedTask; + } - foreach (HypPackageData asset in packageResult.MainPackage) - { - // Try add the package into the list - GameInstallPackage package = new GameInstallPackage(asset, GamePath) + foreach (HypPackageData asset in packageResult.MainPackage) { - PackageType = GameInstallPackageType.General - }; + // Try add the package into the list + GameInstallPackage package = new GameInstallPackage(asset, GamePath) + { + PackageType = GameInstallPackageType.General + }; + + // Add the main package + packageList.Add(package); + LogWriteLine($"Adding general package: {package.Name} to the list (Hash: {package.HashString})", + LogType.Default, true); + } - // Add the main package - packageList.Add(package); - LogWriteLine($"Adding general package: {package.Name} to the list (Hash: {package.HashString})", - LogType.Default, true); + return ValueTask.CompletedTask; + } + catch (Exception exception) + { + return ValueTask.FromException(exception); } } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously @@ -2814,10 +2823,10 @@ protected virtual async ValueTask TryAddOtherInstalledVoicePacks( // Try get the voice over resource if (TryGetVoiceOverResourceByLocaleCode(packageDetail.AudioPackage, localeCode, - out HypPackageData outRes)) + out HypPackageData? outRes)) { // Check if the existing package is already exist or not. - GameInstallPackage outResDup = + GameInstallPackage? outResDup = packageList.FirstOrDefault(x => x.LanguageID != null && x.LanguageID.Equals(outRes.Language, StringComparison.OrdinalIgnoreCase)); @@ -3219,7 +3228,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsCompleted = false; Status.IsCanceled = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Update); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Update); #endif break; case CompletenessStatus.Completed: @@ -3230,7 +3239,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif // HACK: Fix the progress not achieving 100% while completed lock (Progress) @@ -3247,7 +3256,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif break; case CompletenessStatus.Idle: @@ -3258,7 +3267,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif break; } diff --git a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.PkgVersion.cs index 95b1d1bfea..3781cc7b96 100644 --- a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.PkgVersion.cs @@ -90,7 +90,7 @@ protected override ValueTask ParsePkgVersions2FileInfo(List pkgFi pkgFileInfo.Add(assetLocalInfo); } - string? execPrefix = Path.GetFileNameWithoutExtension(GameVersionManager?.GamePreset.GameExecutableName); + string? execPrefix = Path.GetFileNameWithoutExtension(GameVersionManager.GamePreset.GameExecutableName); if (string.IsNullOrEmpty(execPrefix)) { return ValueTask.CompletedTask; diff --git a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs index 5182be4b77..84c599db23 100644 --- a/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs @@ -64,7 +64,7 @@ protected override string _gameAudioLangListPath private string _gameAudioOldPath => Path.Combine(_gameDataPath, "StreamingAssets", "Audio", "GeneratedSoundBanks", "Windows"); - private GenshinRepair Repair { get; set; } + private GenshinRepair Repair { get; } #endregion diff --git a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs index c8d86257da..8c6dd374e1 100644 --- a/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Honkai/HonkaiInstall.cs @@ -27,32 +27,35 @@ // ReSharper disable IdentifierTypo // ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault // ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +#pragma warning disable IDE0130 +#nullable enable namespace CollapseLauncher.InstallManager.Honkai { internal sealed partial class HonkaiInstall : InstallManagerBase { #region Override Properties - protected override bool _canDeltaPatch => GameVersionManager.IsGameHasDeltaPatch(); - protected override DeltaPatchProperty _gameDeltaPatchProperty => GameVersionManager.GetDeltaPatchInfo(); + protected override bool _canDeltaPatch => GameVersionManager.IsGameHasDeltaPatch(); + protected override DeltaPatchProperty? _gameDeltaPatchProperty => GameVersionManager.GetDeltaPatchInfo(); #endregion #region Properties - private HonkaiRepairV2 _gameRepairManager { get; set; } + private HonkaiRepairV2 _gameRepairManager { get; } #endregion - public HonkaiInstall(UIElement parentUI, IGameVersion gameVersionManager, IGameSettings gameSettings) + public HonkaiInstall(UIElement parentUI, IGameVersion gameVersionManager, IGameSettings gameSettings, IRepair gameRepairManager) : base(parentUI, gameVersionManager, gameSettings) { + _gameRepairManager = (HonkaiRepairV2)gameRepairManager; } #region Public Methods - public override async ValueTask StartPackageVerification(List gamePackage) + public override async ValueTask StartPackageVerification(List? gamePackage) { IsRunning = true; @@ -64,7 +67,7 @@ public override async ValueTask StartPackageVerification(List StartPackageVerification(List - new HonkaiRepairV2(ParentUI, - GameVersionManager, - GameSettings!, - versionString, - true, - false); -#nullable restore - - protected override async Task StartPackageInstallationInner(List gamePackage = null, + new(ParentUI, + GameVersionManager, + GameSettings!, + versionString, + true); + + protected override async Task StartPackageInstallationInner(List? gamePackage = null, bool isOnlyInstallPackage = false, bool doNotDeleteZipExplicit = false) { @@ -101,8 +101,8 @@ protected override async Task StartPackageInstallationInner(List TryShowFailedGameConversionState() { // Get the target and source path - string gamePath = GameVersionManager.GameDirPath; - string gamePathIngredients = GetFailedGameConversionFolder(gamePath); + string gamePath = GameVersionManager.GameDirPath; + string? gamePathIngredients = GetFailedGameConversionFolder(gamePath); // If path doesn't exist or null, then return false if (gamePathIngredients is null || !Directory.Exists(gamePathIngredients)) { @@ -137,24 +137,24 @@ public override async ValueTask TryShowFailedGameConversionState() #region Private Methods - Utilities - private string GetFailedGameConversionFolder(string basepath) + private string? GetFailedGameConversionFolder(string basepath) { try { // Step back once from the game directory - string ParentPath = Path.GetDirectoryName(basepath); + string? parentPath = Path.GetDirectoryName(basepath); // Get the ingredient path - if (ParentPath != null) + if (!string.IsNullOrEmpty(parentPath)) { - string IngredientPath = Directory - .EnumerateDirectories(ParentPath, + string? ingredientPath = Directory + .EnumerateDirectories(parentPath, $"{GameVersionManager.GamePreset.GameDirectoryName}*_ConvertedTo-*_Ingredients", SearchOption.TopDirectoryOnly) .FirstOrDefault(); // If the path is not null, then return - if (IngredientPath is not null) + if (ingredientPath is not null) { - return IngredientPath; + return ingredientPath; } } } diff --git a/CollapseLauncher/Classes/InstallManagement/StarRail/StarRailInstall.cs b/CollapseLauncher/Classes/InstallManagement/StarRail/StarRailInstall.cs index 8922496f06..c82af0b955 100644 --- a/CollapseLauncher/Classes/InstallManagement/StarRail/StarRailInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/StarRail/StarRailInstall.cs @@ -71,7 +71,7 @@ public StarRailInstall(UIElement parentUI, IGameVersion gameVersionManager, IGam #region Public Methods - public override async ValueTask StartPackageVerification(List gamePackage) + public override async ValueTask StartPackageVerification(List? gamePackage) { IsRunning = true; @@ -104,7 +104,7 @@ protected override async Task StartPackageInstallationInner( bool doNotDeleteZipExplicit = false) { // If the delta patch is performed, then return - if (!isOnlyInstallPackage && await StartDeltaPatch(_gameRepairManager, false, true)) + if (!isOnlyInstallPackage && await StartDeltaPatch(_gameRepairManager!, false, true)) { // Assign the game package to delta-patch requirement list // and start the additional patching process (like Audio patch, etc) diff --git a/CollapseLauncher/Classes/InstallManagement/Zenless/ZenlessInstall.cs b/CollapseLauncher/Classes/InstallManagement/Zenless/ZenlessInstall.cs index 09e75d1738..ebc96b1a56 100644 --- a/CollapseLauncher/Classes/InstallManagement/Zenless/ZenlessInstall.cs +++ b/CollapseLauncher/Classes/InstallManagement/Zenless/ZenlessInstall.cs @@ -70,7 +70,7 @@ public ZenlessInstall(UIElement parentUI, IGameVersion gameVersionManager, IGame #region Override Methods - StartPackageInstallationInner - public override async ValueTask StartPackageVerification(List gamePackage) + public override async ValueTask StartPackageVerification(List? gamePackage) { IsRunning = true; @@ -80,7 +80,7 @@ public override async ValueTask StartPackageVerification(List UpdateSophonFileTotalProgress(read, false); + + protected void UpdateSophonFileTotalProgress(long read, bool forceUpdate) { _ = Interlocked.Add(ref ProgressAllSizeCurrent, read); @@ -523,7 +526,7 @@ protected void UpdateSophonFileTotalProgress(long read) // Calculate the speed for download (just use it for update only by setting receivedBytes to 0) _sophonDownloadOnlySpeed = CalculateSpeed(lastReceivedDownloadBytes, ref _sophonDownloadOnlyLastSpeed, ref _sophonDownloadOnlyReceivedBytes, ref _sophonDownloadOnlyLastTick); - if (!CheckIfNeedRefreshStopwatch()) + if (!CheckIfNeedRefreshStopwatch() && !forceUpdate) { return; } @@ -688,7 +691,7 @@ protected virtual void HttpClientDownloadProgressAdapter(int read, DownloadProgr UpdateAll(); } - protected virtual void HttpClientDownloadProgressAdapter(object sender, DownloadEvent e) + protected virtual void HttpClientDownloadProgressAdapter(object? sender, DownloadEvent e) { // Set the progress bar not undetermined Status.IsProgressPerFileIndetermined = false; diff --git a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs index 1502f46652..685a1a67ea 100644 --- a/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs +++ b/CollapseLauncher/Classes/Interfaces/IGameInstallManager.cs @@ -7,13 +7,14 @@ // ReSharper disable UnusedMemberInSuper.Global // ReSharper disable IdentifierTypo +#nullable enable namespace CollapseLauncher.Interfaces { internal interface IGameInstallManager : IBackgroundActivity, IDisposable { ValueTask GetInstallationPath(bool isHasOnlyMigrateOption = false); Task StartPackageDownload(bool skipDialog = false); - ValueTask StartPackageVerification(List gamePackage = null); + ValueTask StartPackageVerification(List? gamePackage = null); Task StartPackageInstallation(); void ApplyGameConfig(bool forceUpdateToLatest = false); diff --git a/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs b/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs index 4715ce02e3..1afbbad339 100644 --- a/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs +++ b/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CollapseLauncher.Interfaces; +namespace CollapseLauncher.Interfaces; public interface INotifyAllPropertyChanged { diff --git a/CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs b/CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs index 3bd6c0769b..c021c4eae0 100644 --- a/CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs +++ b/CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs @@ -8,6 +8,7 @@ using CollapseLauncher.InstallManager; using CollapseLauncher.InstallManager.Base; using CollapseLauncher.Interfaces; +using CollapseLauncher.Pages; using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool.Parser.AssetIndex; @@ -18,7 +19,6 @@ using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Shared.Region; using Hi3Helper.Win32.ManagedTools; -using CollapseLauncher.Pages; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Animation; @@ -504,17 +504,17 @@ private void UpdateStatusCallback(InstallProgressState delegateState) { using (_updateStatusLock.EnterScope()) { - string stateString = delegateState switch + string? stateString = delegateState switch { - InstallProgressState.Removing => string.Format("Deleting" + ": " + Locale.Current.Lang._Misc.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), - InstallProgressState.Idle => Locale.Current.Lang._Misc.Idle, - InstallProgressState.Install => string.Format(Locale.Current.Lang._Misc.Extracting + ": " + Locale.Current.Lang._Misc.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), - InstallProgressState.Verify or InstallProgressState.Preparing => string.Format(Locale.Current.Lang._Misc.Verifying + ": " + Locale.Current.Lang._Misc.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), - _ => string.Format((!_updateProgressProperty.IsUpdateMode ? Locale.Current.Lang._Misc.Downloading : Locale.Current.Lang._Misc.Updating) + ": " + Locale.Current.Lang._Misc.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal) + InstallProgressState.Removing => string.Format("Deleting" + ": " + Locale.Current.Lang?._Misc?.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), + InstallProgressState.Idle => Locale.Current.Lang?._Misc?.Idle, + InstallProgressState.Install => string.Format(Locale.Current.Lang?._Misc?.Extracting + ": " + Locale.Current.Lang?._Misc?.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), + InstallProgressState.Verify or InstallProgressState.Preparing => string.Format(Locale.Current.Lang?._Misc?.Verifying + ": " + Locale.Current.Lang?._Misc?.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal), + _ => string.Format((!_updateProgressProperty.IsUpdateMode ? Locale.Current.Lang?._Misc?.Downloading : Locale.Current.Lang?._Misc?.Updating) + ": " + Locale.Current.Lang?._Misc?.PerFromTo, _updateProgressProperty.StateCount, _updateProgressProperty.StateCountTotal) }; Status.ActivityStatus = stateString; - Status.ActivityAll = string.Format(Locale.Current.Lang._Misc.PerFromTo, _updateProgressProperty.AssetCount, _updateProgressProperty.AssetCountTotal); + Status.ActivityAll = string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", _updateProgressProperty.AssetCount, _updateProgressProperty.AssetCountTotal); UpdateStatus(); } @@ -534,7 +534,7 @@ private static async Task EnsureDiskSpaceAvailability(string gamePath, long size LogType.Default, true); // Get the information about the disk - DriveInfo driveInfo = new DriveInfo(gamePath); + DriveInfo driveInfo = new(gamePath); // Push log regarding disk space Logger.LogWriteLine($"Total free space remained on disk: {driveInfo.Name}: {ConverterTool.SummarizeSizeSimple(driveInfo.TotalFreeSpace)}.", @@ -571,12 +571,11 @@ public ValueTask MoveGameLocation() return new ValueTask(false); } - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] public async ValueTask UninstallGame() { if (!ComMarshal.TryCastComObjectAs(_gameInstaller, - out IGameUninstaller? asUninstaller, - out Exception? castEx)) + out IGameUninstaller? asUninstaller, + out Exception? castEx)) { Logger.LogWriteLine($"The current plugin interface doesn't implement IGameUninstaller. Function will not be called!\r\n{castEx}", LogType.Error, true); return false; @@ -653,13 +652,13 @@ public async ValueTask CleanUpGameFiles(bool withDialog = true) return; // Collect temp files - DirectoryInfo tempDir = new DirectoryInfo(tempDirPath); + DirectoryInfo tempDir = new(tempDirPath); List tempFiles = []; - long totalSize = 0; + long totalSize = 0; foreach (FileInfo file in tempDir.EnumerateFiles("*", SearchOption.AllDirectories)) { - LocalFileInfo localFile = new LocalFileInfo(file, gameDirPath); + LocalFileInfo localFile = new(file, gameDirPath); tempFiles.Add(localFile); totalSize += file.Length; } @@ -721,7 +720,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsCompleted = false; Status.IsCanceled = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Update); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Update); #endif break; case CompletenessStatus.Completed: @@ -732,7 +731,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif lock (Progress) { @@ -748,7 +747,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif break; case CompletenessStatus.Idle: @@ -759,7 +758,7 @@ public void UpdateCompletenessStatus(CompletenessStatus status) Status.IsProgressAllIndetermined = false; Status.IsProgressPerFileIndetermined = false; #if !DISABLEDISCORD - InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle); + InnerLauncherConfig.AppDiscordPresence.SetActivity(ActivityType.Idle); #endif break; } diff --git a/CollapseLauncher/Classes/Plugins/PluginImporter.ZipPluginSource.cs b/CollapseLauncher/Classes/Plugins/PluginImporter.ZipPluginSource.cs index 5c6a335814..84bc39082f 100644 --- a/CollapseLauncher/Classes/Plugins/PluginImporter.ZipPluginSource.cs +++ b/CollapseLauncher/Classes/Plugins/PluginImporter.ZipPluginSource.cs @@ -74,7 +74,7 @@ public static async Task GetSourceFrom(string sourceFilePath, C bool isManifestCompressed = manifestEntry.Name.EndsWith(CompressionExt, StringComparison.OrdinalIgnoreCase); string? eliminatedPrefix = Path.GetDirectoryName(manifestEntry.FullName); - await using Stream manifestStream = manifestEntry.Open(); + await using Stream manifestStream = await manifestEntry.OpenAsync(token); await using Stream manifestDecStream = isManifestCompressed ? new BrotliStream(manifestStream, CompressionMode.Decompress) : manifestStream; @@ -98,7 +98,7 @@ public static async Task GetSourceFrom(string sourceFilePath, C { if (isFail) { - archive?.Dispose(); + await (archive?.DisposeAsync() ?? ValueTask.CompletedTask); if (fileStream != null) { await fileStream.DisposeAsync(); diff --git a/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs b/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs index fb510838d8..74ff3d0f40 100644 --- a/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs +++ b/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs @@ -20,8 +20,8 @@ namespace CollapseLauncher.Plugins; public partial class PluginInfo { - private string[]? UpdateCdnList { get; set; } - private IPluginSelfUpdate? Updater { get; set; } + private string[]? UpdateCdnList { get; } + private IPluginSelfUpdate? Updater { get; } public bool IsUpdateSupported { diff --git a/CollapseLauncher/Classes/Plugins/PluginInfo.cs b/CollapseLauncher/Classes/Plugins/PluginInfo.cs index 20b267f21e..7bb42cfd62 100644 --- a/CollapseLauncher/Classes/Plugins/PluginInfo.cs +++ b/CollapseLauncher/Classes/Plugins/PluginInfo.cs @@ -155,17 +155,19 @@ public unsafe PluginInfo(string pluginFilePath, string pluginRelName, PluginMani // TODO: Add versioning check. GameVersion pluginStandardVersion = *((delegate* unmanaged[Cdecl])getPluginStandardVersionHandleP)(); GameVersion pluginVersion = *((delegate* unmanaged[Cdecl])getPluginVersionHandleP)(); - void* pluginInstancePtr = ((delegate* unmanaged[Cdecl])getPluginHandleP)(); + nint pluginInstancePtr = ((delegate* unmanaged[Cdecl])getPluginHandleP)(); - if (pluginInstancePtr == null) + if (pluginInstancePtr == nint.Zero) { throw new NullReferenceException($"Plugin's \"GetPlugin\" ({pluginRelName}) export function returns a null pointer!"); } - IPlugin? pluginInstance = ComInterfaceMarshaller.ConvertToManaged(pluginInstancePtr); - if (pluginInstance == null) + if (!ComMarshal.TryCreateComObjectFromReference(pluginInstancePtr, + out IPlugin? pluginInstance, + out Exception? ex)) { - throw new NullReferenceException($"Plugin's \"GetPlugin\" ({pluginRelName}) export returns an invalid interface contract! Make sure that the plugin returns the valid interface instance!"); + throw new NullReferenceException($"Plugin's \"GetPlugin\" ({pluginRelName}) export returns an invalid interface contract! Make sure that the plugin returns the valid interface instance!", + ex); } // Get Self Updater @@ -441,11 +443,6 @@ public unsafe void Dispose() finally { // Free the plugin handle and remove it from the dictionary. - if (!ComMarshal.TryReleaseComObject(Instance, out Exception? ex)) - { - Logger.LogWriteLine($"[PluginInfo] Cannot release COM Object reference for IPlugin instance due to unexpected error: {ex}", LogType.Error, true); - } - NativeLibrary.Free(Handle); // Free GCHandle and nullify the delegate. diff --git a/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.News.cs b/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.News.cs index 497a703d70..25cf2cbfd9 100644 --- a/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.News.cs +++ b/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.News.cs @@ -22,7 +22,6 @@ private void ConvertNewsAndCarouselEntries(HypLauncherContentApi contentApi) List carouselList = contentApi.Data.Content.Carousel; newsList.Clear(); - contentApi.Data.Content.ResetCachedNews(); carouselList.Clear(); using PluginDisposableMemory newsEntry = PluginDisposableMemoryExtension.ToManagedSpan(_pluginNewsApi.GetNewsEntries); diff --git a/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.cs b/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.cs index a2a954ffc2..c1d87154a3 100644 --- a/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.cs +++ b/CollapseLauncher/Classes/Plugins/PluginLauncherApiWrapper.cs @@ -67,14 +67,14 @@ public PluginLauncherApiWrapper(IPlugin plugin, PluginPresetConfigWrapper preset public HttpClient ApiGeneralHttpClient => throw new NotImplementedException(); public HttpClient ApiResourceHttpClient => throw new NotImplementedException(); - public async ValueTask LoadAsync( - Func? beforeLoadRoutineAsync = null, - Func? afterLoadRoutineAsync = null, - ActionOnTimeOutRetry? onTimeoutRoutine = null, - Action? errorLoadRoutine = null, - CancellationToken token = default) + public async Task LoadAsync( + Func? beforeLoadRoutineAsync = null, + Func? afterLoadRoutineAsync = null, + ActionOnTimeOutRetry? onTimeoutRoutine = null, + Action? errorLoadRoutine = null, + CancellationToken token = default) { - await (beforeLoadRoutineAsync?.Invoke(token) ?? ValueTask.CompletedTask); + await (beforeLoadRoutineAsync?.Invoke(token) ?? Task.CompletedTask); try { @@ -91,7 +91,7 @@ public async ValueTask LoadAsync( ConvertSocialMediaEntries(LauncherGameContent); ConvertNewsAndCarouselEntries(LauncherGameContent); - await(afterLoadRoutineAsync?.Invoke(token) ?? ValueTask.CompletedTask); + await(afterLoadRoutineAsync?.Invoke(token) ?? Task.CompletedTask); return true; } catch (Exception ex) diff --git a/CollapseLauncher/Classes/Plugins/PluginManager.cs b/CollapseLauncher/Classes/Plugins/PluginManager.cs index b50879b4b8..6888a7b215 100644 --- a/CollapseLauncher/Classes/Plugins/PluginManager.cs +++ b/CollapseLauncher/Classes/Plugins/PluginManager.cs @@ -18,10 +18,10 @@ namespace CollapseLauncher.Plugins; #nullable enable internal static partial class PluginManager { - private const string PluginDirPrefix = "Hi3Helper.Plugin.*"; + private const string PluginDirPrefix = "Hi3Helper.Plugin.*"; internal const string ManifestPrefix = "manifest.json"; - public static readonly Dictionary PluginInstances = new(StringComparer.OrdinalIgnoreCase); + public static Dictionary PluginInstances { get; } = new(StringComparer.OrdinalIgnoreCase); internal static async Task LoadPlugins( Dictionary> launcherMetadataConfig, diff --git a/CollapseLauncher/Classes/Plugins/PluginPresetConfigWrapper.cs b/CollapseLauncher/Classes/Plugins/PluginPresetConfigWrapper.cs index 35732d3d2e..bd0578c382 100644 --- a/CollapseLauncher/Classes/Plugins/PluginPresetConfigWrapper.cs +++ b/CollapseLauncher/Classes/Plugins/PluginPresetConfigWrapper.cs @@ -8,7 +8,6 @@ using Hi3Helper.Plugin.Core.Management.PresetConfig; using Hi3Helper.Plugin.Core.Utility; using Hi3Helper.Shared.Region; -using Hi3Helper.Win32.ManagedTools; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -388,23 +387,6 @@ public void Dispose() { _config.Free(); DiscordPresenceContext.Dispose(); - - ReleaseComObject(PluginNewsApi); - ReleaseComObject(PluginMediaApi); - ReleaseComObject(PluginGameManager); - ReleaseComObject(PluginGameInstaller); - ReleaseComObject(_config); - GC.SuppressFinalize(this); - return; - - static void ReleaseComObject(T obj) - where T : class - { - if (!ComMarshal.TryReleaseComObject(obj, out Exception? ex)) - { - Logger.LogWriteLine($"[PluginPresetConfigWrapper::Dispose] Cannot release COM Instance of {typeof(T).Name}\r\n{ex}", LogType.Error, true); - } - } } } diff --git a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs index 0213dbf41f..3851f3d258 100644 --- a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs +++ b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs @@ -1,4 +1,6 @@ using CollapseLauncher.Helper; +using Hi3Helper.Data; +using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; using System.Collections.Generic; using System.Drawing; @@ -15,7 +17,7 @@ internal static #if !DEBUG readonly #endif - Dictionary WindowSizeProfiles + Dictionary WindowSizeProfiles #if DEBUG => #else @@ -24,7 +26,7 @@ Dictionary WindowSizeProfiles new() { { - "Normal", + WindowSizeProfile.Normal, new WindowSizeProp { WindowBounds = new Size(1280, 720), @@ -50,7 +52,7 @@ Dictionary WindowSizeProfiles } }, { - "Small", + WindowSizeProfile.Small, new WindowSizeProp { WindowBounds = new Size(1024, 576), @@ -77,21 +79,21 @@ Dictionary WindowSizeProfiles } }; - internal static string CurrentWindowSizeName + internal static WindowSizeProfile CurrentWindowSizeName { get { - string val = GetAppConfigValue("WindowSizeProfile").ToString(); - return !WindowSizeProfiles.ContainsKey(val ?? "Normal") ? WindowSizeProfiles.Keys.FirstOrDefault() : val; + WindowSizeProfile profile = GetAppConfigValue("WindowSizeProfile").ToEnum(); + return !WindowSizeProfiles.ContainsKey(profile) ? WindowSizeProfiles.Keys.FirstOrDefault() : profile; } set { - SetAppConfigValue("WindowSizeProfile", value); + SetAppConfigValue("WindowSizeProfile", IniValue.Create(value)); WindowUtility.SetWindowSize(CurrentWindowSize.WindowBounds.Width, CurrentWindowSize.WindowBounds.Height); } } - internal static WindowSizeProp CurrentWindowSize { get => WindowSizeProfiles[CurrentWindowSizeName]; } + internal static WindowSizeProp CurrentWindowSize { get => WindowSizeProfiles[CurrentWindowSizeName]; } } internal class WindowSizeProp diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index eab953c770..9cb5a6642d 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -1,15 +1,16 @@ using CollapseLauncher.Extension; using CollapseLauncher.Helper; +using CollapseLauncher.Interfaces.Class; using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.SentryHelper; -using Hi3Helper.Shared.Region; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; @@ -19,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using Velopack.Sources; +using WinRT; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; #pragma warning disable IDE0130 @@ -59,8 +61,7 @@ await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, relativePath, - cancelToken - ); + cancelToken); } catch (Exception ex) { @@ -137,6 +138,77 @@ public async Task DownloadString(string url, timeout); } + #region CDN Property + [GeneratedBindableCustomProperty] + public partial class CDNURLProperty : NotifyPropertyChanged + { + public required string URLPrefix + { + get; + init + { + field = value; + OnPropertyChanged(); + } + } + + public required string Name + { + get; + init + { + field = value; + OnPropertyChanged(); + } + } + + public required string Description + { + get; + init + { + field = value; + OnPropertyChanged(); + } + } + + public bool PartialDownloadSupport + { + get; + init + { + field = value; + OnPropertyChanged(); + } + } + + public bool Equals(CDNURLProperty? other) => other?.GetHashCode() == GetHashCode(); + + public override int GetHashCode() => HashCode.Combine(URLPrefix, Name, PartialDownloadSupport); + + public override bool Equals([NotNullWhen(true)] object? obj) => + obj is CDNURLProperty asCdnProperty && Equals(asCdnProperty); + + public static bool operator ==(CDNURLProperty from, CDNURLProperty to) + => from.Equals(to); + + public static bool operator !=(CDNURLProperty from, CDNURLProperty to) + => !(from == to); + + public static bool operator ==(object? from, CDNURLProperty to) + => to.Equals(from); + + public static bool operator !=(object? from, CDNURLProperty to) + => !(from == to); + + public static bool operator ==(CDNURLProperty from, object? to) + => from.Equals(to); + + public static bool operator !=(CDNURLProperty from, object? to) + => !(from == to); + } + #endregion + internal static class FallbackCDNUtil { private static HttpClient _client; diff --git a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs index f77e1bc073..014add3925 100644 --- a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs +++ b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs @@ -39,64 +39,69 @@ public sealed partial class MainPage private static string RegionToChangeName => MetadataHelper.GetCurrentTranslatedTitleRegion(); - private List LastMenuNavigationItem; - private List LastFooterNavigationItem; - private readonly Dictionary<(string, string), bool> RegionLoadingStatus = new(); + private CancellationTokenSourceWrapper? regionLoadingCts; - private async Task LoadRegionFromCurrentConfigV2(PresetConfig preset, string gameName, string gameRegion) + private Task LoadRegionFromCurrentConfigV2(PresetConfig preset, string gameName, string gameRegion) { - if (RegionLoadingStatus.ContainsKey((gameName,gameRegion))) + TaskCompletionSource tcs = new(); + Interlocked.Exchange(ref regionLoadingCts, new CancellationTokenSourceWrapper())?.Dispose(); + string regionToChangeName = $"{preset.GameLauncherApi?.GameNameTranslation} - {preset.GameLauncherApi?.GameRegionTranslation}"; + + if (!RegionLoadingStatus.TryAdd((gameName,gameRegion), false)) { LogWriteLine($"Region {gameName} - {gameRegion} is already loading, aborting...", LogType.Warning, true); - return false; + return Task.FromResult(false); } - RegionLoadingStatus.Add((gameName, gameRegion), false); - - CancellationTokenSourceWrapper tokenSource = new(); - string regionToChangeName = $"{preset.GameLauncherApi?.GameNameTranslation} - {preset.GameLauncherApi?.GameRegionTranslation}"; - bool runResult = await (preset.GameLauncherApi? - .LoadAsync(BeforeLoadRoutine, - AfterLoadRoutine, - ActionOnTimeOutRetry, - OnErrorRoutine, - tokenSource.Token) ?? ValueTask.FromResult(false)); - - RegionLoadingStatus.Remove((gameName, gameRegion)); - return runResult; + new Thread(Impl) { IsBackground = false }.Start(); + return tcs.Task; + + async void Impl() + { + try + { + bool runResult = await (preset.GameLauncherApi? + .LoadAsync(BeforeLoadRoutine, + AfterLoadRoutine, + ActionOnTimeOutRetry, + OnErrorRoutine, + regionLoadingCts?.Token ?? CancellationToken.None) ?? Task.FromResult(false)); + + RegionLoadingStatus.Remove((gameName, gameRegion)); + tcs.SetResult(runResult); + } + catch (Exception ex) + { + OnErrorRoutineInner(ex, ErrorType.Unhandled); + tcs.SetResult(false); + } + } void OnErrorRoutine(Exception ex) => OnErrorRoutineInner(ex, ErrorType.Unhandled); void OnErrorRoutineInner(Exception ex, ErrorType errorType) { Interlocked.Exchange(ref IsLoadRegionComplete, true); - LoadingMessageHelper.HideActionButton(); - LoadingMessageHelper.HideLoadingFrame(); + DispatcherQueue.TryEnqueue(() => + { + LoadingMessageHelper.HideActionButton(); + LoadingMessageHelper.HideLoadingFrame(); - LogWriteLine($"Error has occurred while loading: {regionToChangeName}!\r\n{ex}", LogType.Scheme, true); - ErrorSender.SendExceptionWithoutPage(ex, errorType); - MainFrameChanger.ChangeWindowFrame(typeof(DisconnectedPage)); + LogWriteLine($"Error has occurred while loading: {regionToChangeName}!\r\n{ex}", LogType.Scheme, true); + ErrorSender.SendExceptionWithoutPage(ex, errorType); + MainFrameChanger.ChangeWindowFrame(typeof(DisconnectedPage)); + }); } void CancelLoadEvent(object sender, RoutedEventArgs args) { - tokenSource.Cancel(); + regionLoadingCts?.Cancel(); Interlocked.Exchange(ref IsLoadRegionComplete, true); DispatcherQueue.TryEnqueue(() => { // If explicit cancel was triggered, restore the navigation menu item then return false - foreach (object item in LastMenuNavigationItem) - { - NavigationViewControl.MenuItems.Add(item); - } - foreach (object item in LastFooterNavigationItem) - { - NavigationViewControl.FooterMenuItems.Add(item); - } NavigationViewControl.IsSettingsVisible = true; - LastMenuNavigationItem.Clear(); - LastFooterNavigationItem.Clear(); m_arguments.StartGame?.Play = false; ChangeRegionConfirmProgressBar.Visibility = Visibility.Collapsed; @@ -106,7 +111,7 @@ void CancelLoadEvent(object sender, RoutedEventArgs args) }); } - async ValueTask AfterLoadRoutine(CancellationToken token) + async Task AfterLoadRoutine(CancellationToken token) { try { @@ -132,8 +137,8 @@ async ValueTask AfterLoadRoutine(CancellationToken token) DispatcherQueueExtensions.TryEnqueue(() => { + // NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems.OfType().FirstOrDefault(); OnPropertyChanged(nameof(CurrentPresetConfig)); - OnPropertyChanged(nameof(CurrentGameBackgroundData)); }); } catch (Exception ex) @@ -151,20 +156,29 @@ void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int time LoadingMessageHelper.ShowActionButton(Locale.Current.Lang?._Misc?.Cancel, "", CancelLoadEvent); } - async ValueTask BeforeLoadRoutine(CancellationToken token) + Task BeforeLoadRoutine(CancellationToken token) { try { - Interlocked.Exchange(ref IsLoadRegionComplete, false); - KeyboardShortcuts.CannotUseKbShortcuts = true; // Disable keyboard shortcuts while loading region - LogWriteLine($"Initializing game: {regionToChangeName}...", LogType.Scheme, true); + try + { + Interlocked.Exchange(ref IsLoadRegionComplete, false); + KeyboardShortcuts.CannotUseKbShortcuts = true; // Disable keyboard shortcuts while loading region + LogWriteLine($"Initializing game: {regionToChangeName}...", LogType.Scheme, true); + + ClearMainPageState(); + _ = ShowAsyncLoadingTimedOutPill(token); + } + catch (Exception ex) + { + OnErrorRoutineInner(ex, ErrorType.Unhandled); + } - await Task.Run(ClearMainPageState, token); - _ = ShowAsyncLoadingTimedOutPill(token); + return Task.CompletedTask; } - catch (Exception ex) + catch (Exception exception) { - OnErrorRoutineInner(ex, ErrorType.Unhandled); + return Task.FromException(exception); } } } @@ -173,13 +187,6 @@ private void ClearMainPageState() { DispatcherQueue.TryEnqueue(() => { - // Clear NavigationViewControl Items and Reset Region props - LastMenuNavigationItem = [.. NavigationViewControl.MenuItems]; - LastFooterNavigationItem = [.. NavigationViewControl.FooterMenuItems]; - NavigationViewControl.MenuItems.Clear(); - NavigationViewControl.FooterMenuItems.Clear(); - NavigationViewControl.IsSettingsVisible = false; - // Clear cache on navigation reset LauncherFrame.BackStack.Clear(); int cacheSizeOld = LauncherFrame.CacheSize; @@ -203,7 +210,7 @@ private async Task FinalizeLoadRegion(string gameName, string gameRegion, Cancel CurrentGameProperty = GamePropertyVault.GetCurrentGameProperty(); // Init NavigationPanel Items - await Task.Run(() => InitializeNavigationItems(), token); + InitializeNavigationItems(); } private async Task LoadGameStaticsByGameType(PresetConfig preset, string gameName, string gameRegion, CancellationToken token) @@ -297,7 +304,7 @@ private static bool IsNotificationTimestampValid(NotificationProp Entry) return isBeginValid && isEndValid; } - private async void ChangeRegion(object sender, RoutedEventArgs e) + private void ChangeRegion(object? sender, RoutedEventArgs? e) { try { @@ -310,7 +317,7 @@ private async void ChangeRegion(object sender, RoutedEventArgs e) } } - private async void ChangeRegionNoWarning(object sender, RoutedEventArgs e) + private async void ChangeRegionNoWarning(object? sender, RoutedEventArgs? e) { try { @@ -358,10 +365,11 @@ private async void ChangeRegionInstant() } } - private async Task LoadRegionRootButton() + private async Task LoadRegionRootButton() { if (ComboBoxGameTitle.SelectedValue is not string gameTitle || - ComboBoxGameRegion.SelectedValue is not PresetConfig gameRegion) return false; + ComboBoxGameRegion.SelectedValue is not PresetConfig gameRegion) + return; // Set and Save CurrentRegion in AppConfig MetadataHelper.SaveGame(gameTitle, gameRegion.ZoneName); @@ -373,7 +381,7 @@ private async Task LoadRegionRootButton() _ = ShowAsyncLoadingTimedOutPill(); if (!await LoadRegionFromCurrentConfigV2(gameRegion, gameTitle, gameRegion.ZoneName ?? "")) { - return false; + return; } LogWriteLine($"Region changed to {gameRegion.ZoneFullname}", LogType.Scheme, true); @@ -381,11 +389,9 @@ private async Task LoadRegionRootButton() if (AppDiscordPresence.IsRpcEnabled) AppDiscordPresence.SetupPresence(gameRegion); #endif - return true; - } - private void ToggleChangeRegionBtn(in object sender, bool IsHide) + private void ToggleChangeRegionBtn(object? sender, bool IsHide) { if (IsHide) { diff --git a/CollapseLauncher/Classes/RegistryMonitor/RegistryMonitor.cs b/CollapseLauncher/Classes/RegistryMonitor/RegistryMonitor.cs index 926d961c8d..0988466dc3 100644 --- a/CollapseLauncher/Classes/RegistryMonitor/RegistryMonitor.cs +++ b/CollapseLauncher/Classes/RegistryMonitor/RegistryMonitor.cs @@ -1,9 +1,4 @@ -// ReSharper disable CommentTypo -// ReSharper disable PartialTypeWithSinglePart -// ReSharper disable StringLiteralTypo -// ReSharper disable UnusedMember.Global - -/* +/* * Credit * ============================================================================================= * Original code by: Thomas Freudenberg ©2003 - 2005 @@ -15,337 +10,386 @@ */ using Hi3Helper; +using Hi3Helper.Data; using Hi3Helper.SentryHelper; -using Hi3Helper.Win32.Native.Enums; +using Hi3Helper.Win32.Native.Enums.Registry; using Hi3Helper.Win32.Native.LibraryImport; using Hi3Helper.Win32.Native.Structs; using Microsoft.Win32; using System; +using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Threading; -using static Hi3Helper.Logger; +// ReSharper disable CommentTypo +// ReSharper disable PartialTypeWithSinglePart +// ReSharper disable StringLiteralTypo +// ReSharper disable UnusedMember.Global +// ReSharper disable CheckNamespace -namespace RegistryUtils +#nullable enable +namespace CollapseLauncher.RegistryUtils; + +public sealed partial class RegistryMonitor : IDisposable { - public sealed partial class RegistryMonitor : IDisposable + #region Event handling + + /// + /// Occurs when the specified registry key has changed. + /// + public event EventHandler? RegChanged; + + /// + /// Occurs when the access to the registry fails. + /// + public event ErrorEventHandler? Error; + + /// + /// Raises the event. + /// + /// + ///

+ /// OnRegChanged is called when the specified registry key has changed. + ///

+ /// + /// When overriding in a derived class, be sure to call + /// the base class's method. + /// + ///
+ private void OnRegChanged() => RegChanged?.Invoke(this, null!); + + /// + /// Raises the event. + /// + /// The which occured while watching the registry. + /// + /// + private void OnError(Exception e) { - #region Event handling - - /// - /// Occurs when the specified registry key has changed. - /// - public event EventHandler RegChanged; - - /// - /// Raises the event. - /// - /// - ///

- /// OnRegChanged is called when the specified registry key has changed. - ///

- /// - /// When overriding in a derived class, be sure to call - /// the base class's method. - /// - ///
- private void OnRegChanged() - { - EventHandler handler = RegChanged; - handler?.Invoke(this, null!); - } +#if DEBUG + Logger.LogWriteLine($"[RegistryMonitor] An error has occurred:\r\n" + + $" Hive: {_registryHive}\r\n" + + $" SubKey: {_registrySubKey}" + + $" Error: {e}", LogType.Debug, true); +#endif + + SentryHelper.ExceptionHandler(e, SentryHelper.ExceptionType.UnhandledOther); + Error?.Invoke(this, new ErrorEventArgs(e)); + } - /// - /// Occurs when the access to the registry fails. - /// - public event ErrorEventHandler Error; - - /// - /// Raises the event. - /// - /// The which occured while watching the registry. - /// - ///

- /// OnError is called when an exception occurs while watching the registry. - ///

- /// - /// When overriding in a derived class, be sure to call - /// the base class's method. - /// - ///
- private void OnError(Exception e) - { - SentryHelper.ExceptionHandler(e, SentryHelper.ExceptionType.UnhandledOther); - ErrorEventHandler handler = Error; - handler?.Invoke(this, new ErrorEventArgs(e)); - } + #endregion - #endregion + #region Private member variables - #region Private member variables + private const RegChangeNotifyFilter DefaultRegFilter = + RegChangeNotifyFilter.Key + | RegChangeNotifyFilter.Attribute + | RegChangeNotifyFilter.Value + | RegChangeNotifyFilter.Security; - private HKEYCLASS _registryHive; - private string _registrySubName; - private readonly Lock _threadLock = new(); - private Thread _thread; - private bool _disposed; - private readonly ManualResetEvent _eventTerminate = new(false); + private readonly HKEY _registryHive; + private readonly string _registrySubKey; + private Thread? _thread; + private readonly Lock _threadLock = new(); + private bool _disposed; + private readonly ManualResetEvent _eventTerminate = new(false); - private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute | - RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security; + #endregion - #endregion + /// + /// Initializes a new instance of the class. + /// + /// The registry key to monitor. + public RegistryMonitor(RegistryKey registryKey) : this(registryKey.Name) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The registry key to monitor. - public RegistryMonitor(RegistryKey registryKey) - { - InitRegistryKey(registryKey.Name); - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The subpath of the key or full path of the registry key, which include these valid Hive names:
+ /// + /// - HKEY_CLASSES_ROOT
+ /// - HKEY_CURRENT_CONFIG
+ /// - HKEY_CURRENT_USER
+ /// - HKEY_LOCAL_MACHINE
+ /// - HKEY_PERFORMANCE_DATA
+ /// - HKEY_USERS + ///
+ /// If no valid Hive name is found at the beginning of the path, + /// HKEY_CURRENT_USER or the value from will be used by default. + ///
+ /// And if is provided, the root key of HKEY_CURRENT_USER or will be used by default. + /// + /// + /// The registry hive to be used for monitoring. If a valid hive name from is defined and + /// both the value from is defined too, the hive from will + /// be used instead. + /// + public RegistryMonitor(string? subKeyOrFullKeyPath, HKEY? registryHive = null) + { + _registryHive = registryHive ?? GetRegistryHiveFromName(subKeyOrFullKeyPath) ?? HKEY.HKEY_CURRENT_USER; + _registrySubKey = GetRegistrySubKeyFromName(subKeyOrFullKeyPath); + RegistryNotifyFilter = DefaultRegFilter; - /// - /// Initializes a new instance of the class. - /// - /// The name. - public RegistryMonitor(string name) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); +#if DEBUG + Logger.LogWriteLine($"RegistryMonitor Initialized!\r\n" + + $" Hive: {_registryHive}\r\n" + + $" subKey: {_registrySubKey}", LogType.Debug, true); +#endif + } + + #region String Utils - InitRegistryKey(name); + private const string PathSeparators = @"\/"; + private const string UnderscoreSeparators = "_-"; + + private static readonly Dictionary AlternateHKeyNames = GenerateAlternateHKeyNames(); + + private static readonly Dictionary.AlternateLookup> AlternateHKeyNamesL = + AlternateHKeyNames.GetAlternateLookup>(); + + private static HKEY? GetRegistryHiveFromName(ReadOnlySpan fullName) + { + ReadOnlySpan firstSplit = ConverterTool.GetSplit(fullName, 0, PathSeparators); + return Enum.TryParse(firstSplit, true, out HKEY key) || + AlternateHKeyNamesL.TryGetValue(firstSplit, out key) ? key : null; + } + + private static string GetRegistrySubKeyFromName(ReadOnlySpan fullName) + { + if (fullName.IsEmpty) + { + return ""; } + + fullName = fullName.Trim(PathSeparators); - /// - /// Initializes a new instance of the class. - /// - /// The registry hive. - /// The sub key. - public RegistryMonitor(RegistryHive registryHive, string subKey) + HKEY? keyHive = GetRegistryHiveFromName(fullName); + ReadOnlySpan firstSplit = ConverterTool.GetSplit(keyHive.HasValue ? fullName : ReadOnlySpan.Empty, + 0, + PathSeparators); + + if (firstSplit.IsEmpty) { - InitRegistryKey(registryHive, subKey); -#if DEBUG - LogWriteLine($"RegistryMonitor Initialized!\r\n" + - $" Hive: {registryHive}\r\n" + - $" subKey: {subKey}", LogType.Debug, true); -#endif + return fullName.ToString(); } - ~RegistryMonitor() => Dispose(); + int offset = (int)firstSplit.GetByteOffsetFromSource(fullName) + + firstSplit.Length; - /// - /// Disposes this object. - /// - public void Dispose() + return fullName[offset..].Trim(PathSeparators).ToString(); + } + + private static Dictionary GenerateAlternateHKeyNames() + { + Dictionary returnDict = new(StringComparer.OrdinalIgnoreCase); + Dictionary.AlternateLookup> returnDictL = + returnDict.GetAlternateLookup>(); + Span buffer = stackalloc char[64]; + + foreach (HKEY value in Enum.GetValues()) { - try + if (!Enum.TryFormat(value, buffer, out int written)) { - Stop(); + continue; } - catch (Exception ex) - { - LogWriteLine($"Error at stopping RegistryWatcher!\r\n{ex}", LogType.Error, true); - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - throw new Exception($"Error in RegistryMonitor Dispose routine!\r\n{ex}"); - } - _disposed = true; -#if DEBUG - LogWriteLine("RegistryMonitor Disposed!", LogType.Debug, true); -#endif - GC.SuppressFinalize(this); + + ReadOnlySpan name = buffer[..written]; + ReadOnlySpan nameNoPrefix = GetNameWithoutPrefix(name); + + // Add general name with and without prefix + returnDictL.TryAdd(name, value); + returnDictL.TryAdd(nameNoPrefix, value); + + // Add general name with and without prefix for no underscore version + string nameNoUnderscore = GetNameNoUnderscore(name); + string nameNoUnderscoreNoPrefix = GetNameNoUnderscore(nameNoPrefix); + + returnDict.TryAdd(nameNoUnderscore, value); + returnDict.TryAdd(nameNoUnderscoreNoPrefix, value); } - /// - /// Gets or sets the RegChangeNotifyFilter. - /// - public RegChangeNotifyFilter RegChangeNotifyType + return returnDict; + + static string GetNameNoUnderscore(ReadOnlySpan name) { - get { return _regFilter; } - set + return string.Create(name.Length, name, Action); + + static void Action(Span span, ReadOnlySpan source) { - using (_threadLock.EnterScope()) + int index = 0; + foreach (char c in source) { - if (IsMonitoring) - throw new InvalidOperationException("Monitoring thread is already running"); - - _regFilter = value; + if (!UnderscoreSeparators.Contains(c)) + { + span[index++] = c; + } } + + span[index..].Clear(); } } - #region Initialization - - private void InitRegistryKey(RegistryHive hive, string name) + static ReadOnlySpan GetNameWithoutPrefix(ReadOnlySpan name) { - _registryHive = hive switch - { - RegistryHive.ClassesRoot => HKEYCLASS.HKEY_CLASSES_ROOT, - RegistryHive.CurrentConfig => HKEYCLASS.HKEY_CURRENT_CONFIG, - RegistryHive.CurrentUser => HKEYCLASS.HKEY_CURRENT_USER, - RegistryHive.LocalMachine => HKEYCLASS.HKEY_LOCAL_MACHINE, - RegistryHive.PerformanceData => HKEYCLASS.HKEY_PERFORMANCE_DATA, - RegistryHive.Users => HKEYCLASS.HKEY_USERS, - _ => throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive)) - }; - _registrySubName = name; - } + const string prefix = "HKEY"; - private void InitRegistryKey(string name) - { - string[] nameParts = name.Split('\\'); + return name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + ? name[prefix.Length..].Trim(UnderscoreSeparators) // Slice + : name; // Use previous span + } + } + + #endregion - switch (nameParts[0]) - { - case "HKEY_CLASSES_ROOT": - case "HKCR": - _registryHive = HKEYCLASS.HKEY_CLASSES_ROOT; - break; - - case "HKEY_CURRENT_USER": - case "HKCU": - _registryHive = HKEYCLASS.HKEY_CURRENT_USER; - break; - - case "HKEY_LOCAL_MACHINE": - case "HKLM": - _registryHive = HKEYCLASS.HKEY_LOCAL_MACHINE; - break; - - case "HKEY_USERS": - _registryHive = HKEYCLASS.HKEY_USERS; - break; - - case "HKEY_CURRENT_CONFIG": - _registryHive = HKEYCLASS.HKEY_CURRENT_CONFIG; - break; - - default: - _registryHive = HKEYCLASS.None; - throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", nameof(name)); - } + ~RegistryMonitor() => Dispose(); - _registrySubName = string.Join("\\", nameParts, 1, nameParts.Length - 1); + /// + /// Disposes this object. + /// + public void Dispose() + { + Interlocked.Exchange(ref _disposed, true); + + try + { + Stop(); } + catch (Exception ex) + { + Logger.LogWriteLine($"Error at stopping RegistryWatcher!\r\n{ex}", LogType.Error, true); + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + throw new Exception($"Error in RegistryMonitor Dispose routine!\r\n{ex}"); + } +#if DEBUG + Logger.LogWriteLine("RegistryMonitor Disposed!", LogType.Debug, true); +#endif + GC.SuppressFinalize(this); + } + + /// + /// Gets or sets the . + /// + public RegChangeNotifyFilter RegistryNotifyFilter { get; set; } - #endregion + /// + /// if the monitoring thread is running, otherwise . + /// + private bool IsMonitoring => _thread != null; - /// - /// true if this object is currently monitoring; - /// otherwise, false. - /// - private bool IsMonitoring + /// + /// Start monitoring. + /// + public void Start() + { + if (IsMonitoring) { - get { return _thread != null; } + return; } - /// - /// Start monitoring. - /// - public void Start() - { - if (_disposed) - throw new ObjectDisposedException(null, "This instance is already disposed"); + if (Volatile.Read(ref _disposed)) + throw new ObjectDisposedException(null, "This instance is already disposed"); - using (_threadLock.EnterScope()) + using (_threadLock.EnterScope()) + { + _eventTerminate.Reset(); + Interlocked.Exchange(ref _thread, new Thread(MonitorThread) { - if (IsMonitoring) - { - return; - } - - _eventTerminate.Reset(); - _thread = new Thread(MonitorThread) - { - IsBackground = true - }; - _thread.Start(); - } + IsBackground = true + }); + _thread.Start(); } + } - /// - /// Stops the monitoring thread. - /// - public void Stop() + /// + /// Stops the monitoring thread. + /// + public void Stop() + { + if (Volatile.Read(ref _disposed) || + Volatile.Read(ref _thread) == null) { - if (_disposed) return; + return; + } - using (_threadLock.EnterScope()) - { - Thread thread = _thread; - if (thread == null) - { - return; - } + using (_threadLock.EnterScope()) + { + _eventTerminate.Set(); + _thread?.Join(); + } + } - _eventTerminate.Set(); - thread.Join(); - } + private void MonitorThread() + { + try + { + ThreadLoop(); + } + catch (Exception e) + { + OnError(e); } - private void MonitorThread() + Interlocked.Exchange(ref _thread, null); + } + + private void ThreadLoop() + { + HResult result = + PInvoke.RegOpenKeyEx(_registryHive, + _registrySubKey, + RegOption.NonVolatile, + RegSAM.Read | RegSAM.QueryValue | RegSAM.Notify, + out nint registryKey); + + Exception? resultEx = result.GetException(); + if (resultEx != null) { - try - { - ThreadLoop(); - } - catch (Exception e) - { - OnError(e); - } - _thread = null; + OnError(resultEx); + return; } - private void ThreadLoop() + AutoResetEvent eventNotify = new(false); + try { - int result = PInvoke.RegOpenKeyEx(_registryHive, - _registrySubName, - 0, - (uint)ACCESS_MASK.STANDARD_RIGHTS_READ | (uint)RegKeyAccess.KEY_QUERY_VALUE | (uint)RegKeyAccess.KEY_NOTIFY, - out var registryKey); - if (result != 0) - throw new Win32Exception(result); - - AutoResetEvent eventNotify = new AutoResetEvent(false); - try - { - WaitHandle[] waitHandles = [eventNotify, _eventTerminate]; + WaitHandle[] waitHandles = [eventNotify, _eventTerminate]; - while (!_eventTerminate.WaitOne(0, true)) + while (!_eventTerminate.WaitOne(0, true)) + { + if (_disposed) break; + + int resultNotify = PInvoke.RegNotifyChangeKeyValue(registryKey, + true, + RegistryNotifyFilter, + eventNotify.SafeWaitHandle, + true); + if (resultNotify != 0) + throw new Win32Exception(resultNotify); + + int waitHandlerAny = WaitHandle.WaitAny(waitHandles); + if (waitHandlerAny != 0) { - if (_disposed) break; - - int resultNotify = PInvoke.RegNotifyChangeKeyValue(registryKey, - true, - _regFilter, - eventNotify.SafeWaitHandle, - true); - if (resultNotify != 0) - throw new Win32Exception(resultNotify); - - int waitHandlerAny = WaitHandle.WaitAny(waitHandles); - if (waitHandlerAny != 0) - { - continue; - } + continue; + } #if DEBUG - LogWriteLine($"[RegistryMonitor] Found change(s) in registry!\r\n" + - $" Hive: {_registryHive}\r\n" + - $" subName: {_registrySubName}", LogType.Debug, true); + Logger.LogWriteLine($"[RegistryMonitor] Found change(s) in registry!\r\n" + + $" Hive: {_registryHive}\r\n" + + $" SubKey: {_registrySubKey}", LogType.Debug, true); #endif - OnRegChanged(); - } + OnRegChanged(); } - finally + } + finally + { + if (registryKey != nint.Zero) { - if (registryKey != IntPtr.Zero) - { - ((HResult)PInvoke.RegCloseKey(registryKey)).ThrowOnFailure(); - } - - eventNotify.Dispose(); + ((HResult)PInvoke.RegCloseKey(registryKey)).ThrowOnFailure(); } + + eventNotify.Dispose(); } } } diff --git a/CollapseLauncher/Classes/RepairManagement/BSDiff.cs b/CollapseLauncher/Classes/RepairManagement/BSDiff.cs index b67d2a9e8a..2acd64909c 100644 --- a/CollapseLauncher/Classes/RepairManagement/BSDiff.cs +++ b/CollapseLauncher/Classes/RepairManagement/BSDiff.cs @@ -5,11 +5,13 @@ using Hi3Helper.Data; using System; +using System.Buffers; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; using System.Threading; // ReSharper disable CommentTypo // ReSharper disable CheckNamespace @@ -241,7 +243,7 @@ private long[] ReadControlNumbers(Stream source, long newPosition) /// /// /// - public unsafe void Apply(CancellationToken token = default) + public void Apply(CancellationToken token = default) { // check if apply can proceed if (!CanContinueApply) @@ -253,126 +255,154 @@ public unsafe void Apply(CancellationToken token = default) ProgressStopwatch.Restart(); Progress = new BinaryPatchProgress(); - if (InputStream.CanSeek) InputStream.Position = 0; + if (InputStream.CanSeek) InputStream.Position = 0; if (OutputStream.CanSeek) OutputStream.Position = 0; // preallocate buffers for reading and writing - Span newData = stackalloc byte[CBufferSize]; - Span oldData = stackalloc byte[CBufferSize]; - - // decompress each part (to read it) - using (Stream controlCompStream = PatchStream()) - using (Stream diffCompStream = PatchStream()) - using (Stream extraCompStream = PatchStream()) - using (Stream controlStream = TryGetCompressionStream(controlCompStream, CHeaderSize)) - using (Stream diffStream = TryGetCompressionStream(diffCompStream, CHeaderSize + ControlLength)) - using (Stream extraStream = TryGetCompressionStream(extraCompStream, CHeaderSize + ControlLength + DiffLength)) - { - // ReSharper disable once RedundantAssignment - Span control = stackalloc long[3]; - // ReSharper disable once UnusedVariable - Span buffer = stackalloc byte[8]; - - long oldPosition = 0; - long newPosition = 0; - while (newPosition < NewSize) - { - // Get the control array - control = ReadControlNumbers(controlStream, newPosition); - - // Get the size to copy - long bytesToCopy = control[0]; - - // Seek old file to the position that the new data is diffed against - InputStream.Position = oldPosition; - - // Start the copy process - while (bytesToCopy > 0) - { - // Throw if cancelation is called - token.ThrowIfCancellationRequested(); - - // Get minimum size to copy - int actualBytesToCopy = (int)Math.Min(bytesToCopy, CBufferSize); - // Get the minimum size from old data to copy - int availableInputBytes = (int)Math.Min(actualBytesToCopy, InputStream.Length - InputStream.Position); - - // Read diff and old data - diffStream.ReadExactly(newData[..actualBytesToCopy]); - InputStream.ReadExactly(oldData[..availableInputBytes]); - - // Add the old with new data in vectors - fixed (byte* newDataPtr = &newData[0]) - fixed (byte* oldDataPtr = &oldData[0]) - { - // Get the offset and remained offset - int offset; - long offsetRemained = CBufferSize % Vector128.Count; - for (offset = 0; offset < CBufferSize - offsetRemained; offset += Vector128.Count) - { - Vector128 newVector = Sse2.LoadVector128(newDataPtr + offset); - Vector128 oldVector = Sse2.LoadVector128(oldDataPtr + offset); - Vector128 resultVector = Sse2.Add(newVector, oldVector); - - Sse2.Store(newDataPtr + offset, resultVector); - } - - // Process the remained data by the last offset - while (offset < CBufferSize) *(newDataPtr + offset) += *(oldDataPtr + offset++); - - // Write the data into the output - OutputStream.Write(newData[..actualBytesToCopy]); - - // Adjust counters - newPosition += actualBytesToCopy; - oldPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - - // Update progress - UpdateProgress(OutputStream.Length, NewSize, actualBytesToCopy); - } - } - - // SANITY CHECK: Check if the new position + Additional/new data has more size than _newSize. - // If yes, then throw. - if (newPosition + control[1] > NewSize) - { - throw new InvalidDataException($"The patch file is corrupted! newPosition + control[1] ({newPosition} + {control[1]}) > newSize ({NewSize})"); - } - - // Get the bytes to copy for the additional data (new data) - bytesToCopy = (int)control[1]; - while (bytesToCopy > 0) - { - // Throw if cancelation is called - token.ThrowIfCancellationRequested(); - - // Get the size of the additional data to copy - int actualBytesToCopy = (int)Math.Min(bytesToCopy, CBufferSize); - - // Read the new data from extra stream and write it to output - extraStream.ReadExactly(newData[..actualBytesToCopy]); - OutputStream.Write(newData[..actualBytesToCopy]); - - newPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - - // Update progress - UpdateProgress(OutputStream.Length, NewSize, actualBytesToCopy); - } - - // Adjust the position (either move it towards or behind the current position) - oldPosition += control[2]; - } - } - - if (!LeaveOpenStream) + byte[] bufferData = ArrayPool.Shared.Rent(CBufferSize * 2); + Span newData = bufferData.AsSpan(0, CBufferSize); + Span oldData = bufferData.AsSpan(CBufferSize); + + try { - InputStream.Dispose(); - OutputStream.Dispose(); - } + // decompress each part (to read it) + using Stream controlCompStream = PatchStream(); + using Stream diffCompStream = PatchStream(); + using Stream extraCompStream = PatchStream(); + + const long controlOffset = CHeaderSize; + long diffOffset = controlOffset + ControlLength; + long extraOffset = diffOffset + DiffLength; + using Stream controlStream = TryGetCompressionStream(controlCompStream, controlOffset); + using Stream diffStream = TryGetCompressionStream(diffCompStream, diffOffset); + using Stream extraStream = TryGetCompressionStream(extraCompStream, extraOffset); + + long oldPosition = 0; + long newPosition = 0; + + while (newPosition < NewSize) + { + // Get the control array + Span control = ReadControlNumbers(controlStream, newPosition); + + // Get the size to copy + long bytesToCopy = control[0]; + + // Seek old file to the position that the new data is diffed against + InputStream.Position = oldPosition; + + // Start the copy process + while (bytesToCopy > 0) + { + // Throw if cancelation is called + token.ThrowIfCancellationRequested(); + + // Get minimum size to copy + int actualBytesToCopy = (int)Math.Min(bytesToCopy, CBufferSize); + // Get the minimum size from old data to copy + int availableInputBytes = (int)Math.Min(actualBytesToCopy, InputStream.Length - InputStream.Position); + + // Read diff and old data + diffStream.ReadExactly(newData[..actualBytesToCopy]); + InputStream.ReadExactly(oldData[..availableInputBytes]); + + // Add the old with new data in vectors + ref byte newDataRef = ref MemoryMarshal.GetReference(newData); + ref byte oldDataRef = ref MemoryMarshal.GetReference(oldData); + + // Get the offset and remained offset + int offset = 0; + + // Try with 256-bit vectors first + if (Vector256.IsHardwareAccelerated) + { + long offsetRemained = CBufferSize % Vector256.Count; + for (offset = 0; offset < CBufferSize - offsetRemained; offset += Vector256.Count) + { + Vector256 newVector = Vector256.LoadUnsafe(ref newDataRef, (nuint)offset); + Vector256 oldVector = Vector256.LoadUnsafe(ref oldDataRef, (nuint)offset); + Vector256 resultVector = Vector256.Add(newVector, oldVector); + + resultVector.StoreUnsafe(ref newDataRef, (nuint)offset); + } + } + // Fall back to 128-bit vectors + else if (Vector128.IsHardwareAccelerated) + { + long offsetRemained = CBufferSize % Vector128.Count; + for (offset = 0; offset < CBufferSize - offsetRemained; offset += Vector128.Count) + { + Vector128 newVector = Vector128.LoadUnsafe(ref newDataRef, (nuint)offset); + Vector128 oldVector = Vector128.LoadUnsafe(ref oldDataRef, (nuint)offset); + Vector128 resultVector = Vector128.Add(newVector, oldVector); + + resultVector.StoreUnsafe(ref newDataRef, (nuint)offset); + } + } + + // Process the remained data by the last offset + while (offset < CBufferSize) + { + Unsafe.Add(ref newDataRef, offset) += Unsafe.Add(ref oldDataRef, offset); + offset++; + } + + // Write the data into the output + OutputStream.Write(newData[..actualBytesToCopy]); + + // Adjust counters + newPosition += actualBytesToCopy; + oldPosition += actualBytesToCopy; + bytesToCopy -= actualBytesToCopy; + + // Update progress + UpdateProgress(OutputStream.Length, NewSize, actualBytesToCopy); + } + + // SANITY CHECK: Check if the new position + Additional/new data has more size than _newSize. + // If yes, then throw. + if (newPosition + control[1] > NewSize) + { + throw new InvalidDataException($"The patch file is corrupted! newPosition + control[1] ({newPosition} + {control[1]}) > newSize ({NewSize})"); + } + + // Get the bytes to copy for the additional data (new data) + bytesToCopy = control[1]; + while (bytesToCopy > 0) + { + // Throw if cancelation is called + token.ThrowIfCancellationRequested(); + + // Get the size of the additional data to copy + int actualBytesToCopy = (int)Math.Min(bytesToCopy, CBufferSize); + + // Read the new data from extra stream and write it to output + extraStream.ReadExactly(newData[..actualBytesToCopy]); + OutputStream.Write(newData[..actualBytesToCopy]); + + newPosition += actualBytesToCopy; + bytesToCopy -= actualBytesToCopy; + + // Update progress + UpdateProgress(OutputStream.Length, NewSize, actualBytesToCopy); + } + + // Adjust the position (either move it towards or behind the current position) + oldPosition += control[2]; + } + + if (!LeaveOpenStream) + { + InputStream.Dispose(); + OutputStream.Dispose(); + } - CanContinueApply = false; + CanContinueApply = false; + } + finally + { + ArrayPool.Shared.Return(bufferData); + } } private static unsafe long ReadInt64Sanity(ReadOnlySpan buf) diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index 51f617b37f..b2aa4759dc 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -4,7 +4,7 @@ using CollapseLauncher.Helper.StreamUtility; using CollapseLauncher.InstallManager.Genshin; using Hi3Helper; -using Hi3Helper.EncTool; +using Hi3Helper.EncTool.Enc; using Hi3Helper.EncTool.Hashes; using Hi3Helper.EncTool.Parser.AssetIndex; using Hi3Helper.EncTool.Parser.YSDispatchHelper; @@ -139,9 +139,9 @@ internal async Task BuildPrimaryManifest(DownloadClient TryDeleteDownloadPref(); // Get Sophon Properties - string gameAudioListPath = Path.Combine(GamePath, $"{ExecPrefix}_Data", "Persistent", "audio_lang_14"); - SophonChunkUrls? sophonManifestUrls = GameVersionManager?.GamePreset.LauncherResourceChunksURL; - HttpClient httpClient = downloadClient.GetHttpClient(); + string gameAudioListPath = Path.Combine(GamePath, $"{ExecPrefix}_Data", "Persistent", "audio_lang_14"); + SophonChunkUrls? sophonManifestUrls = GameVersionManager.GamePreset.LauncherResourceChunksURL; + HttpClient httpClient = downloadClient.GetHttpClient(); if (sophonManifestUrls == null) { @@ -305,7 +305,7 @@ private async Task GetDispatcherQuery(HttpClient client, Cancella // DEBUG ONLY: Show encrypted Proto as JSON+Base64 format string dFormat = $"Query Response (RAW Encrypted form):\r\n{dispatchInfo?.Content}"; #if DEBUG - LogWriteLine(dFormat); + LogWriteLine(dFormat, LogType.Debug, true); #endif // Write the decrypted query response in the log (for diagnostic) await LogFileWriter.WriteLineAsync(dFormat); @@ -323,7 +323,7 @@ private async Task TryDecryptAndParseDispatcher(DispatchInfo disp // DEBUG ONLY: Show the decrypted Proto as Base64 format string dFormat = $"Proto Response (RAW Decrypted form):\r\n{Convert.ToBase64String(decryptedData)}"; #if DEBUG - LogWriteLine(dFormat); + LogWriteLine(dFormat, LogType.Debug, true); #endif await LogFileWriter.WriteLineAsync(dFormat); diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Cache.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Cache.cs index 3970895d09..4a4859a5b8 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Cache.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Cache.cs @@ -6,6 +6,7 @@ using Hi3Helper; using Hi3Helper.Data; using Hi3Helper.EncTool; +using Hi3Helper.EncTool.Enc; using Hi3Helper.EncTool.Parser.AssetMetadata; using Hi3Helper.EncTool.Parser.KianaDispatch; using Hi3Helper.Plugin.Core.Management; @@ -141,7 +142,7 @@ private static async Task> DeserializeCacheAssetListAsync currentJsonEntry in packageVersionText.GetStringEnumeration()) @@ -194,8 +195,14 @@ private static async Task> DeserializeCacheAssetListAsync GetGameServerInfoAsync( private static AudioLanguageType GetCurrentGameAudioLanguage(PresetConfig presetConfig) { using RegistryKey? rootRegistryKey = Registry.CurrentUser.OpenSubKey(presetConfig.ConfigRegistryLocation); - if (rootRegistryKey?.GetValue(PersonalAudioSetting.ValueName) is not byte[] jsonValue) - { - return presetConfig.GameDefaultCVLanguage; - } + return GetAudioLanguageTypeFromString((rootRegistryKey?.GetValue(PersonalAudioSetting.ValueName) as byte[])? + .Deserialize(HonkaiSettingsJsonContext.Default.PersonalAudioSetting)?._userCVLanguage) + ?? presetConfig.GameDefaultCVLanguage; - PersonalAudioSetting? audioSetting = - jsonValue.Deserialize(HonkaiSettingsJsonContext.Default.PersonalAudioSetting); - if (audioSetting == null) + static AudioLanguageType? GetAudioLanguageTypeFromString(string? lang) { - return presetConfig.GameDefaultCVLanguage; - } + if (lang?.StartsWith("Japanese") ?? false) + { + return AudioLanguageType.Japanese; + } - if (audioSetting - ._userCVLanguage? - .StartsWith("Chinese", StringComparison.OrdinalIgnoreCase) ?? false) - { - return AudioLanguageType.Chinese; - } + if (lang?.StartsWith("Chinese") ?? false) + { + return AudioLanguageType.Chinese; + } - // Use default value based on preset. - return presetConfig.GameDefaultCVLanguage; + return null; + } } } \ No newline at end of file diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Video.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Video.cs index a8926a9367..ed26b6b476 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Video.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Video.cs @@ -7,6 +7,7 @@ using Hi3Helper.EncTool.Parser.AssetMetadata; using Hi3Helper.EncTool.Parser.CacheParser; using Hi3Helper.EncTool.Parser.KianaDispatch; +using Hi3Helper.Plugin.Core.Management; using Hi3Helper.Preset; using Hi3Helper.Shared.ClassStruct; using System; @@ -34,8 +35,8 @@ internal static partial class AssetBundleExtension internal static void RemoveUnlistedVideoAssetFromList(this List originList, List assetListFromVideo) { - List originOthersListOnly = originList.Where(x => x.FT != FileType.Video).ToList(); - List originVideoListOnly = originList.Where(x => x.FT == FileType.Video).ToList(); + List originOthersListOnly = [.. originList.Where(x => x.FT != FileType.Video)]; + List originVideoListOnly = [.. originList.Where(x => x.FT == FileType.Video)]; originList.Clear(); originList.AddRange(originOthersListOnly); @@ -58,7 +59,9 @@ internal static async Task> AudioLanguageType gameLanguageType = GetCurrentGameAudioLanguage(presetConfig); int parallelThread = Math.Clamp(progressibleInstance.ThreadForIONormalized * 4, 16, 64); - HashSet ignoredCgHashset = new(ignoredCgIds ?? []); + GameVersion currentVersion = progressibleInstance.GameVersion; + + HashSet ignoredCgHashset = [.. ignoredCgIds ?? []]; List assetInfoList = await assetBundleHttpClient .GetCacheAssetBundleListAsync(presetConfig, @@ -128,19 +131,22 @@ async ValueTask ImplCheckAndAdd(KeyValuePair entry, Cancel string assetUrl = baseUrl.CombineURLFromString("Video", assetName); // Update status - progressibleInstance.Status.ActivityStatus = string.Format(Locale.Current.Lang?._GameRepairPage?.Status14 ?? "", assetName); - progressibleInstance.Status.IsProgressAllIndetermined = true; + progressibleInstance.Status.ActivityStatus = + string.Format(Locale.Current.Lang?._GameRepairPage?.Status14 ?? "", assetName); + progressibleInstance.Status.IsProgressAllIndetermined = true; progressibleInstance.Status.IsProgressPerFileIndetermined = true; progressibleInstance.UpdateStatus(); - if (entry.Value.Category is CGCategory.Birthday or CGCategory.Activity or CGCategory.VersionPV) + if (entry.Value.Category is CGCategory.Birthday or CGCategory.Activity or CGCategory.VersionPV || + IsCgOnCurrentVersionOrPresent(assetName, currentVersion)) { UrlStatus urlStatus = await assetBundleHttpClient.GetURLStatusCode(assetUrl, innerToken); Logger.LogWriteLine($"The CG asset: {assetName} " + - (urlStatus.IsSuccessStatusCode ? "is" : "is not") + $" available (Status code: {urlStatus.StatusCode})", LogType.Default, true); + (urlStatus.IsSuccessStatusCode ? "is" : "is not") + + $" available (Status code: {urlStatus.StatusCode})", LogType.Default, true); if (!urlStatus.IsSuccessStatusCode) { - throw new HttpRequestException("No Asset bundle URLs were reachable"); + return; } if (urlStatus.FileSize > 0) @@ -161,5 +167,9 @@ async ValueTask ImplCheckAndAdd(KeyValuePair entry, Cancel }); } } + + static bool IsCgOnCurrentVersionOrPresent(string assetName, GameVersion gameVersion) => + GameVersion.TryParse(assetName.GetSplit(0, '_'), out GameVersion fileVersion) && + fileVersion >= gameVersion; } } diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Check.Generic.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Check.Generic.cs index 74a044ee65..1dbb343594 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Check.Generic.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Check.Generic.cs @@ -5,7 +5,6 @@ using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.EncTool.Hashes; -using Hi3Helper.EncTool.Parser.CacheParser; using Hi3Helper.EncTool.Parser.Senadina; using Hi3Helper.Shared.ClassStruct; using System; @@ -55,7 +54,7 @@ private async Task IsHashMatchedAuto( /// Check actual remote size asset from the actual URL.
///
/// Note: This method only works on asset type with URL defined. - /// Otherwise, the method will immediately returns . + /// Otherwise, the method will immediately return . /// private async ValueTask TryIsAssetRemoteSizeEquals( FilePropertiesRemote asset, @@ -63,7 +62,7 @@ private async ValueTask TryIsAssetRemoteSizeEquals( bool useFastCheck, CancellationToken token = default) { - if (!fileInfo.Exists) + if (!fileInfo.Exists || fileInfo.Length == 0) { return false; } @@ -76,7 +75,7 @@ private async ValueTask TryIsAssetRemoteSizeEquals( } UrlStatus status = await HttpClientAssetBundle.GetCachedUrlStatus(asset.RN, token); - if (!status.IsSuccessStatusCode || status.FileSize == 0) // Returns true if status is not successful or size is 0 anyways + if (!status.IsSuccessStatusCode || status.FileSize == 0) // Returns true if status is not successful or size is 0 anyway { return true; } @@ -163,7 +162,7 @@ private async ValueTask TryIsAssetRemoteSizeEquals( { case 8: { - MhyMurmurHash264B hasher = new((uint)asset.S); + MhyMurmurHash264B hasher = new((ulong)assetFileInfo.Length); HashUtility hashUtil = HashUtility.ThreadSafe; (resultStatus, _) = diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Fetch.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Fetch.cs index 0de584d8b7..b25d5f7a34 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Fetch.cs @@ -319,14 +319,20 @@ private async Task FinalizeAudioAssetsPath(List originAsse FilePropertiesRemote? audioDefaultManifestAsset = originAssetList.FirstOrDefault(x => x.N.EndsWith("AUDIO_Default_manifest.m")); // Edit: 2026-05-07 - // Starting from 8.8, AUDIO_Default_manifest.m must expect to be existed. So, if the file is missing or hash doesn't match, + // Starting from 8.8, AUDIO_Default_manifest.m must be present. So, if the file is missing or hash doesn't match, // perform download in the background. Then write AUDIO_Default_Version.txt after. - string audioDefaultVersionPath = Path.Combine(audioBasePath, "AUDIO_Default_Version.txt"); + // + // Edit: 2026-05-30 + // So turns out, I was an idiot. The audioDefaultAssetHash wasn't actually generated from the hash of the AUDIO_Default_manifest.m, + // but it was the first 8 bytes of the hash of the AUDIO_Default file itself, being read as a Big-Endian UInt64. + // I was a bit exagerated while looking at the reversed code and I thought it was reading the hash from the .m file LMFAO. + string audioDefaultVersionPath = Path.Combine(audioBasePath, "AUDIO_Default_Version.txt"); string audioDefaultManifestPath = Path.Combine(audioBasePath, "AUDIO_Default_manifest.m"); - if (await EnsureAudioDefaultManifestExisted(HttpClientGeneric, audioDefaultManifestAsset, audioDefaultManifestPath, token)) + if (audioDefaultAsset != null && + await EnsureAudioDefaultManifestExisted(HttpClientGeneric, audioDefaultManifestAsset, audioDefaultManifestPath, token)) { - byte[] manifestContent = File.ReadAllBytes(audioDefaultManifestPath); - ulong audioDefaultAssetHash = BinaryPrimitives.ReadUInt64BigEndian(manifestContent.AsSpan(0, 8)); + ReadOnlySpan defaultHashBytes = audioDefaultAsset.Hash; + ulong audioDefaultAssetHash = BinaryPrimitives.ReadUInt64BigEndian(defaultHashBytes[..8]); await File.WriteAllTextAsync(audioDefaultVersionPath, $"{gameVersion}\t{audioDefaultAssetHash}", token); } diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Audio.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Audio.cs index 0a37b146c7..cccec0330f 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Audio.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Audio.cs @@ -72,7 +72,7 @@ private async ValueTask RepairAssetAudioType( { byte[] hashBuffer = new byte[8]; byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - MhyMurmurHash264B hasher = new(patchInfo.PatchFileSize); + MhyMurmurHash264B hasher = new((ulong)patchFileStream.Length); try { diff --git a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Block.cs b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Block.cs index e63f458912..d1bdbbb4bd 100644 --- a/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Block.cs +++ b/CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Repair.Block.cs @@ -72,7 +72,7 @@ private async ValueTask RepairAssetBlockType( { byte[] hashBuffer = new byte[8]; byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - MhyMurmurHash264B hasher = new(patchInfo.PatchSize); + MhyMurmurHash264B hasher = new((ulong)patchFileStream.Length); try { diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs index 39cf6d85c5..028bc2b964 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs @@ -339,7 +339,7 @@ await LoadMetadataFile(instance, await LoadMetadataFile(instance, handleArchive, client, - baseUrl.Audio, + baseUrl.AudioPersistent, "AudioV", aDirAudio, token); @@ -349,7 +349,7 @@ await LoadMetadataFile(instance, await LoadMetadataFile(instance, handleArchive, client, - baseUrl.Video, + baseUrl.VideoPersistent, "VideoV", aDirVideo, token); @@ -525,20 +525,44 @@ private static async ValueTask SaveLocalIndexFiles( string fileUrl = baseUrl.CombineURLFromString(filename); await using Stream networkStream = (await client.TryGetCachedStreamFrom(fileUrl, token: token)).Stream; await using Stream sourceStream = !string.IsNullOrEmpty(saveToLocalDir) +#if DEBUG + ? await CreateLocalStream(networkStream, Path.Combine(saveToLocalDir, filename), token) +#else ? CreateLocalStream(networkStream, Path.Combine(saveToLocalDir, filename)) +#endif : networkStream; await parser.ParseAsync(sourceStream, true, token); return parser; - static Stream CreateLocalStream(Stream thisSourceStream, string filePath) +#if DEBUG + static async ValueTask +#else + static Stream +#endif + CreateLocalStream(Stream thisSourceStream, + string filePath +#if DEBUG + , CancellationToken token) +#else + ) +#endif { FileInfo fileInfo = new FileInfo(filePath) .EnsureCreationOfDirectory() .EnsureNoReadOnly() .StripAlternateDataStream(); +#if DEBUG + FileStream fileStream = fileInfo.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + await thisSourceStream.CopyToAsync(fileStream, token); + fileStream.Flush(true); + fileStream.Position = 0; + + return fileStream; +#else return new CopyToStream(thisSourceStream, fileInfo.Create(), null, true); +#endif } } diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetCsvMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetCsvMetadata.cs index 73128c659c..dd412da73c 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetCsvMetadata.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetCsvMetadata.cs @@ -69,6 +69,12 @@ public static bool Parse(ReadOnlySpan line, out Metadata result) return false; } + const string emptyFlag = "Empty"; + if (line.Equals(emptyFlag, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + const string separators = ",;"; // Include ; as well, just in case. const StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs index 9853059985..cd2a8a3950 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs @@ -3,9 +3,9 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,11 +20,11 @@ namespace CollapseLauncher.RepairManagement.StarRail.Struct.Assets; /// public abstract class StarRailAssetSignaturelessMetadata : StarRailAssetBinaryMetadata { - public StarRailAssetSignaturelessMetadata() : this(null) + protected StarRailAssetSignaturelessMetadata() : this(null) { } - public StarRailAssetSignaturelessMetadata(string? customAssetExtension = null) + protected StarRailAssetSignaturelessMetadata(string? customAssetExtension = null) : base(0, 256, 0, // Leave the rest of it to 0 as this metadata has non-consistent header struct @@ -78,10 +78,13 @@ protected override async ValueTask ReadDataCoreAsync( cancellationToken: token) .ConfigureAwait(false); - Span countBufferSpan = countBuffer; - int parentDataCount = BinaryPrimitives.ReadInt32BigEndian(countBufferSpan); + Span countBufferSpan = countBuffer; + int parentDataCount = BinaryPrimitives.ReadInt32BigEndian(countBufferSpan); // -- Declare constant length for each parent and children data + StarRailBinaryDataHeaderStruct header = Header; + short typeVersion = header.TypeVersionFlag.ReverseEndianess(); + const int parentDataBufferLen = 32; const int childrenDataBufferLen = 12; @@ -89,8 +92,12 @@ protected override async ValueTask ReadDataCoreAsync( byte[] parentDataBuffer = ArrayPool.Shared.Rent(parentDataBufferLen); Memory parentDataBufferMemory = parentDataBuffer.AsMemory(0, parentDataBufferLen); + // -- Allocate v3 string field buffer to the Memory + byte[] v3StringFieldLenBuffer = new byte[2]; + Memory v3StringFieldLenBufferMemory = v3StringFieldLenBuffer.AsMemory(); + // -- Allocate list - DataList = new List(parentDataCount); + DataList = [with(parentDataCount)]; try { @@ -108,11 +115,50 @@ protected override async ValueTask ReadDataCoreAsync( lastPos, out int bytesToSkip, out Metadata result); - DataList.Add(result); // -- Skip children data currentOffset += await dataStream.SeekForwardAsync(bytesToSkip, token) .ConfigureAwait(false); + + switch (typeVersion) + { + // Starting from Star Rail 4.3 (Manifest Ver. 3) update, they are adding additional field which contains the subdirectory of the asset. + // So we need to read the string field, and append the subdirectory to the filename if found. + case >= 3: + // Read the string length + await dataStream.ReadAtLeastAsync(v3StringFieldLenBufferMemory, + v3StringFieldLenBufferMemory.Length, + cancellationToken: token); + short v3StringFieldLen = BinaryPrimitives.ReadInt16BigEndian(v3StringFieldLenBuffer); + + // If there's a string field, read and append it to the filename + if (v3StringFieldLen != 0) + { + byte[] strBuffer = new byte[v3StringFieldLen]; + await dataStream.ReadAtLeastAsync(strBuffer, + strBuffer.Length, + cancellationToken: token); + + string str = Encoding.UTF8.GetString(strBuffer); + result = new Metadata + { + Filename = str.CombineURLFromString(result.Filename), + FileSize = result.FileSize, + Flags = result.Flags, + MD5Checksum = result.MD5Checksum + }; + } + break; + } + + // Dummy read to append into the end-of-struct mark (0x80) + Sanity. + int endOfStructMark = dataStream.ReadByte(); + if (endOfStructMark != 0x80) + { + throw new InvalidDataException($"Game data structure might be changed. Unexpected end-of-struct mark, expect 0x80 but received 0x{endOfStructMark:x2} instead. Please report this issue to Collapse developers."); + } + + DataList.Add(result); } } finally @@ -142,8 +188,7 @@ public static void Parse(ReadOnlySpan buffer, // subDataCount = Number of sub-data struct count // subDataSize = Number of sub-data struct size - // 1 = Unknown offset, seek +1 - bytesToSkip = subDataCount * subDataSize + 1; + bytesToSkip = subDataCount * subDataSize; result = new Metadata { MD5Checksum = md5Hash, diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetBinaryMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetBinaryMetadata.cs index 44cf6bca63..5c6c12207c 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetBinaryMetadata.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetBinaryMetadata.cs @@ -1,5 +1,5 @@ -using System; -using CollapseLauncher.RepairManagement.StarRail.Struct.Assets; +using CollapseLauncher.RepairManagement.StarRail.Struct.Assets; +using System; // ReSharper disable CommentTypo #pragma warning disable IDE0290 // Shut the fuck up diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetMetadataIndex.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetMetadataIndex.cs index d4d962bf93..311b6a35d2 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetMetadataIndex.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/StarRailAssetMetadataIndex.cs @@ -18,7 +18,7 @@ namespace CollapseLauncher.RepairManagement.StarRail.Struct; /// internal class StarRailAssetMetadataIndex : StarRailBinaryDataWritable { - public StarRailAssetMetadataIndex() : this(false, false) + public StarRailAssetMetadataIndex() : this(false) { } diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index e504aa0033..dba69185db 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -16,7 +16,7 @@ $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2026 $(Company) - 1.84.3 + 1.84.4 preview x64 @@ -116,7 +116,7 @@ false @@ -129,8 +129,8 @@ true - Size - Size + Speed + Speed false true true @@ -170,7 +170,7 @@ - + @@ -235,9 +235,6 @@ - @@ -254,26 +251,17 @@ - - - @@ -286,15 +274,15 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -313,20 +301,20 @@ - + - + - + - - + + @@ -515,33 +503,22 @@ - - - - - - - - - - - - - - - + - - + + + + + + + @@ -558,11 +535,26 @@ + + + + - + + + + + + + + + + + + diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index 33db77274e..d130ee0fdf 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -179,8 +179,8 @@ private static void InitAppIcons() uint iconCount = PInvoke.ExtractIconEx(mainModulePath, -1, nint.Zero, nint.Zero, 0); if (iconCount > 0) { - IntPtr[] largeIcons = new IntPtr[1]; - IntPtr[] smallIcons = new IntPtr[1]; + nint[] largeIcons = new nint[1]; + nint[] smallIcons = new nint[1]; nint largeIconsArrayP = Marshal.UnsafeAddrOfPinnedArrayElement(largeIcons, 0); nint smallIconsArrayP = Marshal.UnsafeAddrOfPinnedArrayElement(smallIcons, 0); PInvoke.ExtractIconEx(mainModulePath, 0, largeIconsArrayP, smallIconsArrayP, 1); @@ -418,7 +418,7 @@ public static void SpawnFatalErrorConsole(Exception ex) Console.Error.WriteLine("Activity: Checking for possible fallback/recovery update..."); Console.Error.WriteLine("Press any key to exit or Press 'R' to restart the main thread app..."); - using CancellationTokenSource tokenSource = new CancellationTokenSource(); + using CancellationTokenSource tokenSource = new(); _ = RunCheckForPossibleRecoveryOrFallbackUpdate(tokenSource.Token); if (ConsoleKey.R == Console.ReadKey().Key) @@ -675,7 +675,7 @@ public static string MD5Hash(string path) return Convert.ToHexStringLower(hash); } - private static string restartedFromPidArgKey = "restartedFromPid"; + private const string RestartedFromPidArgKey = "restartedFromPid"; public static void ForceRestart() { @@ -684,7 +684,7 @@ public static void ForceRestart() { FileName = AppExecutablePath, WorkingDirectory = Environment.CurrentDirectory, - Arguments = $"{restartedFromPidArgKey}:{Environment.ProcessId} {string.Join(' ', AppCurrentArgument)}", + Arguments = $"{RestartedFromPidArgKey}:{Environment.ProcessId} {string.Join(' ', AppCurrentArgument)}", UseShellExecute = true }); @@ -699,15 +699,15 @@ private static string[] KillParentPidIfRestartRequested(params string[] args) } string firstArg = args[0]; - if (!firstArg.StartsWith(restartedFromPidArgKey, StringComparison.OrdinalIgnoreCase)) + if (!firstArg.StartsWith(RestartedFromPidArgKey, StringComparison.OrdinalIgnoreCase)) { return args; } - string[] otherArgs = args.Length > 1 ? args[1..] : []; - ReadOnlySpan restartParentPid = ConverterTool.GetSplit(firstArg, 1, ":,#$;"); + string[] otherArgs = args.Length > 1 ? args[1..] : []; + ReadOnlySpan restartParentPid = firstArg.GetSplit(1, ":,#$;"); - // Check for PID. If exist, then kill. + // Check for PID. If existed, then kill. if (!int.TryParse(restartParentPid, out int parentPid) || !ProcessChecker.IsProcessExist(parentPid)) { diff --git a/CollapseLauncher/StaticLib/StaticLib.7z.001 b/CollapseLauncher/StaticLib/StaticLib.7z.001 index 598cca2599..aaf96931c0 100644 Binary files a/CollapseLauncher/StaticLib/StaticLib.7z.001 and b/CollapseLauncher/StaticLib/StaticLib.7z.001 differ diff --git a/CollapseLauncher/StaticLib/StaticLib.7z.002 b/CollapseLauncher/StaticLib/StaticLib.7z.002 index 334f02a696..ed621275d1 100644 Binary files a/CollapseLauncher/StaticLib/StaticLib.7z.002 and b/CollapseLauncher/StaticLib/StaticLib.7z.002 differ diff --git a/CollapseLauncher/StaticLib/StaticLib.7z.003 b/CollapseLauncher/StaticLib/StaticLib.7z.003 index 568be65c9a..2cd3133473 100644 Binary files a/CollapseLauncher/StaticLib/StaticLib.7z.003 and b/CollapseLauncher/StaticLib/StaticLib.7z.003 differ diff --git a/CollapseLauncher/StaticLib/StaticLib.7z.004 b/CollapseLauncher/StaticLib/StaticLib.7z.004 index 5432d3edc9..878dd71b7b 100644 Binary files a/CollapseLauncher/StaticLib/StaticLib.7z.004 and b/CollapseLauncher/StaticLib/StaticLib.7z.004 differ diff --git a/CollapseLauncher/StaticLib/StaticLib.7z.005 b/CollapseLauncher/StaticLib/StaticLib.7z.005 index 991390f153..59dff4aa7d 100644 Binary files a/CollapseLauncher/StaticLib/StaticLib.7z.005 and b/CollapseLauncher/StaticLib/StaticLib.7z.005 differ diff --git a/CollapseLauncher/StaticLib/buildScript/build_staticlib.bat b/CollapseLauncher/StaticLib/buildScript/build_staticlib.bat index 1834ca68f1..cac6edf887 100644 --- a/CollapseLauncher/StaticLib/buildScript/build_staticlib.bat +++ b/CollapseLauncher/StaticLib/buildScript/build_staticlib.bat @@ -8,23 +8,23 @@ set OutputDir=%~dp0..\ set RuntimeLibrary=MultiThreaded :: If you want to enables LTO using Clang, replaces LTOArgs with defined one below (very experimental) -:: set LTOArgs=-flto -:: set CXXFLAGS=%LTOArgs% -EHsc -:: set CFLAGS=%LTOArgs% -EHsc -:: set LDFLAGS=%LTOArgs% /LTCG +:: set CXXFLAGS=-flto +:: set CFLAGS=-flto +:: set LDFLAGS=-flto /LTCG +:: :: Or use the one below if you're using MSVC. -:: set LTOArgs= -:: set CXXFLAGS=-EHsc /GL -:: set CFLAGS=-EHsc /GL +:: set CXXFLAGS=/GL +:: set CFLAGS=-/GL :: set LDFLAGS=/LTCG /GENPROFILE -set LTOArgs= -set CXXFLAGS=-EHsc -set CFLAGS=-EHsc +:: +:: Or if you wanted to disable LTO either for both of them, use this +set CXXFLAGS= +set CFLAGS= set LDFLAGS= -set GenericCMAKEParamNoFlagsNoIntOpt=-DCMAKE_BUILD_TYPE=%BuildType% %Toolchain% -DCMAKE_MSVC_RUNTIME_LIBRARY=%RuntimeLibrary% -set GenericCMAKEParamNoFlags=%GenericCMAKEParamNoFlagsNoIntOpt% -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=true -set GenericCMAKEParam=%GenericCMAKEParamNoFlags% -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" -DCMAKE_EXE_LINKER_FLAGS="%LDFLAGS%" -DCMAKE_EXE_LINKER_FLAGS_INIT="%LDFLAGS%" +set GenericCMAKEParamStaticNoFlagsNoIntOpt=-DCMAKE_BUILD_TYPE=%BuildType% %Toolchain% -DCMAKE_MSVC_RUNTIME_LIBRARY=%RuntimeLibrary% +set GenericCMAKEParamStaticNoFlags=%GenericCMAKEParamStaticNoFlagsNoIntOpt% -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=true +set GenericCMAKEParamStatic=%GenericCMAKEParamStaticNoFlags% -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" -DCMAKE_EXE_LINKER_FLAGS="%LDFLAGS%" -DCMAKE_EXE_LINKER_FLAGS_INIT="%LDFLAGS%" :: Test Toolchain call :ToolchainTest @@ -48,7 +48,8 @@ goto :COMPLETE cd waifu2x-ncnn-vulkan git fetch --all && git pull --all copy /Y ..\patch\waifu2x-ncnn-vulkan\src\CMakeLists.txt src\CMakeLists.txt - cmake . %GenericCMAKEParam% -B vclatestbuild -DCMAKE_POLICY_VERSION_MINIMUM=3.5 || goto :ERR + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStatic% -B vclatestbuild -DCMAKE_POLICY_VERSION_MINIMUM=3.5 || goto :ERR cd vclatestbuild msbuild ALL_BUILD.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR call :CallCopy src\%BuildType%\waifu2x-ncnn-vulkan-static.lib || goto :ERR @@ -69,18 +70,10 @@ goto :COMPLETE git fetch --all && git pull --all copy /Y ..\patch\openmp\runtime\CMakeLists.txt openmp\runtime\CMakeLists.txt cd runtimes - set OldCXXFLAGS=%CXXFLAGS% - set OldCFLAGS=%CFLAGS% - set OldLDFLAGS=%LDFLAGS% - set CXXFLAGS= - set CFLAGS= - set LDFLAGS= - cmake . %GenericCMAKEParamNoFlagsNoIntOpt% -DENABLE_CHECK_TARGETS=FALSE -DLLVM_ENABLE_RUNTIMES=openmp -DLIBOMP_ENABLE_SHARED=FALSE -B vclatestbuild || goto :ERR + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStaticNoFlagsNoIntOpt% -DENABLE_CHECK_TARGETS=FALSE -DLLVM_ENABLE_RUNTIMES=openmp -DLIBOMP_ENABLE_SHARED=FALSE -B vclatestbuild || goto :ERR cd vclatestbuild msbuild ALL_BUILD.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR - set CXXFLAGS=%OldCXXFLAGS% - set CFLAGS=%OldCFLAGS% - set LDFLAGS=%OldLDFLAGS% move openmp\runtime\src\%BuildType%\libomp.lib.lib openmp\runtime\src\%BuildType%\libomp.lib call :CallCopy openmp\runtime\src\%BuildType%\libomp.lib || goto :ERR goto :BACKTOROOT @@ -91,7 +84,8 @@ goto :COMPLETE git clone --recursive https://chromium.googlesource.com/webm/libwebp cd libwebp git fetch --all && git pull --all - cmake . %GenericCMAKEParam% -DBUILD_SHARED_LIBS=0 -DWEBP_LINK_STATIC=1 -DEMSCRIPTEN=OFF -DWEBP_USE_THREAD=ON ^ + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStatic% -DBUILD_SHARED_LIBS=0 -DWEBP_LINK_STATIC=1 -DEMSCRIPTEN=OFF -DWEBP_USE_THREAD=ON ^ -DWEBP_BUILD_ANIM_UTILS=OFF -DWEBP_BUILD_CWEBP=OFF -DWEBP_BUILD_DWEBP=OFF -DWEBP_BUILD_GIF2WEBP=OFF ^ -DWEBP_BUILD_IMG2WEBP=OFF -DWEBP_BUILD_VWEBP=OFF -DWEBP_BUILD_WEBPINFO=OFF ^ -DWEBP_BUILD_WEBPMUX=OFF -DWEBP_BUILD_EXTRAS=OFF -B vclatestbuild || goto :ERR @@ -109,53 +103,59 @@ goto :COMPLETE git clone --recursive https://code.videolan.org/videolan/dav1d cd dav1d git fetch --all && git pull --all - set OLDCXXFLAGS=%CXXFLAGS% - set OLDCFLAGS=%CFLAGS% - set OLDLDFLAGS=%LDFLAGS% - set CXXFLAGS= - set CFLAGS= - set LDFLAGS= - call :SETCLANGCC + if /I not "%Toolchain%" EQU "" ( + call :SETCLANGCC + ) + rmdir /S /Q build mkdir build cd build meson setup .. --wipe --default-library=static || goto :ERR ninja - set CXXFLAGS=%OLDCXXFLAGS% - set CFLAGS=%OLDCFLAGS% - set LDFLAGS=%OLDLDFLAGS% copy src\libdav1d.a src\libdav1d.lib call :CallCopy src\libdav1d.lib || goto :ERR - call :UNSETCLANGCC + if /I not "%Toolchain%" EQU "" ( + call :UNSETCLANGCC + ) cd "%~dp0" + + :: title=Building libheif dependencies - zlib + :: git clone --recursive https://github.com/madler/zlib + :: cd zlib + :: git fetch --all && git pull --all + :: rmdir /S /Q vclatestbuild + :: cmake . %GenericCMAKEParamStatic% -DZLIB_BUILD_STATIC=1 -DZLIB_BUILD_SHARED=0 -B vclatestbuild + :: cd vclatestbuild + :: msbuild zlibstatic.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR + :: move %BuildType%\zs.lib %BuildType%\zlib.lib + :: cd "%~dp0 title=Building libheif dependencies - libde265 git clone --recursive https://github.com/neon-nyan/libde265 cd libde265 git fetch --all && git pull --all copy /Y extra\libde265\de265-version.h libde265\de265-version.h - set OLD_CXXFLAGS=%CXXFLAGS% - set OLD_CFLAGS=%CFLAGS% - set CXXFLAGS=%CXXFLAGS% -msse4.1 -mssse3 -msse2 - set CFLAGS=%CFLAGS% -msse4.1 -mssse3 -msse2 - cmake . %GenericCMAKEParamNoFlags% -DBUILD_SHARED_LIBS=0 -DENABLE_SDL=OFF -B vclatestbuild ^ + if /I not "%Toolchain%" EQU "" ( + set OLD_CXXFLAGS=%CXXFLAGS% + set OLD_CFLAGS=%CFLAGS% + set CXXFLAGS=%CXXFLAGS% -msse4.1 -mssse3 -msse2 -EHsc + set CFLAGS=%CFLAGS% -msse4.1 -mssse3 -msse2 -EHsc + ) else ( + set OLD_CXXFLAGS=%CXXFLAGS% + set OLD_CFLAGS=%CFLAGS% + set CXXFLAGS=%CXXFLAGS% -EHsc + set CFLAGS=%CFLAGS% -EHsc + ) + :: cmake . %GenericCMAKEParamStaticNoFlags% -DBUILD_SHARED_LIBS=0 -DENABLE_SDL=OFF -B vclatestbuild ^ + :: -DSUPPORTS_SSE4_1=1 -DSUPPORTS_SSSE3=1 -DSUPPORTS_SSE2=1 ^ + :: -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStaticNoFlags% -DBUILD_SHARED_LIBS=0 -DENABLE_SDL=OFF -B vclatestbuild ^ -DSUPPORTS_SSE4_1=1 -DSUPPORTS_SSSE3=1 -DSUPPORTS_SSE2=1 ^ -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" cd vclatestbuild - msbuild ALL_BUILD.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR - set CXXFLAGS=%OLD_CXXFLAGS% - set CFLAGS=%OLD_CFLAGS% + msbuild dec265\dec265.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR call :CallCopy libde265\%BuildType%\libde265.lib || goto :ERR cd "%~dp0 - - title=Building libheif dependencies - zlib - git clone --recursive https://github.com/madler/zlib - cd zlib - git fetch --all && git pull --all - cmake . %GenericCMAKEParam% -DZLIB_BUILD_STATIC=1 -DZLIB_BUILD_SHARED=0 -B vclatestbuild - cd vclatestbuild - msbuild zlibstatic.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR - move %BuildType%\zs.lib %BuildType%\zlib.lib - cd "%~dp0 title=Building libheif git clone --recursive https://github.com/strukturag/libheif @@ -167,15 +167,23 @@ goto :COMPLETE cd ..\ set thisdir=%~dp0 copy /Y ..\patch\libheif\CMakeLists.txt CMakeLists.txt - cmake . %GenericCMAKEParam% --preset=release-noplugins -DBUILD_SHARED_LIBS=0 -B vclatestbuild -DENABLE_EXPERIMENTAL_FEATURES=1 -DWITH_REDUCED_VISIBILITY=1 ^ + rmdir /S /Q vclatestbuild + :: cmake . %GenericCMAKEParamStatic% --preset=release-noplugins -DBUILD_SHARED_LIBS=0 -B vclatestbuild -DENABLE_EXPERIMENTAL_FEATURES=1 -DWITH_REDUCED_VISIBILITY=1 ^ + :: -DWITH_LIBDE265=1 -DLIBDE265_INCLUDE_DIR=..\libde265 -DLIBDE265_LIBRARY=%thisdir%\libde265\vclatestbuild\libde265\%BuildType%\libde265.lib ^ + :: -DWITH_DAV1D=1 -DDAV1D_INCLUDE_DIR=..\dav1d\include -DDAV1D_LIBRARY=%thisdir%\dav1d\build\src\libdav1d.lib ^ + :: -DWITH_LIBSHARPYUV=1 -DLIBSHARPYUV_INCLUDE_DIR=..\libwebp -DLIBSHARPYUV_LIBRARY=%thisdir%\libwebp\vclatestbuild\%BuildType%\libsharpyuv.lib ^ + :: -DWITH_HEADER_COMPRESSION=1 -DWITH_UNCOMPRESSED_CODEC=1 -DZLIB_INCLUDE_DIR=..\zlib -DZLIB_LIBRARY=%thisdir%\zlib\vclatestbuild\%BuildType%\zlib.lib || goto :ERR + cmake . %GenericCMAKEParamStaticNoFlags% --preset=release-noplugins -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" ^ + -DBUILD_SHARED_LIBS=0 -B vclatestbuild -DENABLE_EXPERIMENTAL_FEATURES=0 -DWITH_REDUCED_VISIBILITY=1 ^ -DWITH_LIBDE265=1 -DLIBDE265_INCLUDE_DIR=..\libde265 -DLIBDE265_LIBRARY=%thisdir%\libde265\vclatestbuild\libde265\%BuildType%\libde265.lib ^ -DWITH_DAV1D=1 -DDAV1D_INCLUDE_DIR=..\dav1d\include -DDAV1D_LIBRARY=%thisdir%\dav1d\build\src\libdav1d.lib ^ - -DWITH_LIBSHARPYUV=1 -DLIBSHARPYUV_INCLUDE_DIR=..\libwebp -DLIBSHARPYUV_LIBRARY=%thisdir%\libwebp\vclatestbuild\%BuildType%\libsharpyuv.lib ^ - -DWITH_HEADER_COMPRESSION=1 -DWITH_UNCOMPRESSED_CODEC=1 -DZLIB_INCLUDE_DIR=..\zlib -DZLIB_LIBRARY=%thisdir%\zlib\vclatestbuild\%BuildType%\zlib.lib || goto :ERR + -DWITH_HEADER_COMPRESSION=0 -DWITH_UNCOMPRESSED_CODEC=0 || goto :ERR copy /Y ..\patch\libheif\vclatestbuild\libheif\heif_version.h vclatestbuild\libheif\heif_version.h cd vclatestbuild\libheif msbuild heif.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR call :CallCopy %BuildType%\heif.lib || goto :ERR + set CXXFLAGS=%OLD_CXXFLAGS% + set CFLAGS=%OLD_CFLAGS% goto :BACKTOROOT :: Building functions - libjxl @@ -183,13 +191,20 @@ goto :COMPLETE title=Building libjxl git clone --recursive https://github.com/libjxl/libjxl cd libjxl + set OLD_CXXFLAGS=%CXXFLAGS% + set OLD_CFLAGS=%CFLAGS% + set CXXFLAGS=%CXXFLAGS% -EHsc + set CFLAGS=%CFLAGS% -EHsc git fetch --all && git pull --all - cmake . %GenericCMAKEParam% -DBUILD_TESTING=OFF -DJPEGXL_STATIC=1 -B vclatestbuild || goto :ERR + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStaticNoFlags% -DCMAKE_CXX_FLAGS="%CXXFLAGS%" -DCMAKE_C_FLAGS="%CFLAGS%" -DBUILD_TESTING=OFF -DJPEGXL_STATIC=1 -B vclatestbuild || goto :ERR cd vclatestbuild msbuild ALL_BUILD.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR call :CallCopy lib\%BuildType%\jxl.lib || goto :ERR call :CallCopy lib\%BuildType%\jxl_cms.lib || goto :ERR call :CallCopy third_party\highway\%BuildType%\hwy.lib || goto :ERR + set CXXFLAGS=%OLD_CXXFLAGS% + set CFLAGS=%OLD_CFLAGS% goto :BACKTOROOT :: Building functions - libzstd @@ -198,7 +213,8 @@ goto :COMPLETE git clone --recursive https://github.com/facebook/zstd cd zstd git fetch --all && git pull --all - cmake . %GenericCMAKEParam% -DBUILD_TESTING=OFF -DZSTD_USE_STATIC_RUNTIME=ON -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_MULTITHREAD_SUPPORT=ON -B vclatestbuild || goto :ERR + rmdir /S /Q vclatestbuild + cmake . %GenericCMAKEParamStatic% -DBUILD_TESTING=OFF -DZSTD_USE_STATIC_RUNTIME=ON -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_MULTITHREAD_SUPPORT=ON -B vclatestbuild || goto :ERR cd vclatestbuild\build\cmake\lib msbuild libzstd_static.vcxproj -p:Configuration=%BuildType% /m:%NUMBER_OF_PROCESSORS% || goto :ERR move %BuildType%\zstd_static.lib %BuildType%\libzstd_static.lib diff --git a/CollapseLauncher/XAMLs/MainApp/DisconnectedPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/DisconnectedPage.xaml.cs index e6ba0445e4..deba191d5d 100644 --- a/CollapseLauncher/XAMLs/MainApp/DisconnectedPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/DisconnectedPage.xaml.cs @@ -2,7 +2,6 @@ using CollapseLauncher.Helper; using CollapseLauncher.Helper.Metadata; using Hi3Helper; -using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; @@ -51,7 +50,7 @@ private void InitializeRegionComboBox() ComboBoxGameTitle.ItemsSource = gameTitleList; ComboBoxGameRegion.ItemsSource = gameRegionList; - int indexCategory = gameTitleList.IndexOf(gameTitle ?? ""); + int indexCategory = gameTitleList.IndexOf(gameTitle); if (indexCategory < 0) indexCategory = 0; int indexRegion = MetadataHelper.GetLastSavedGameRegionIndexOrDefault(gameTitle); diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs index fb5a4ec0f2..ceebd6165b 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs @@ -31,6 +31,7 @@ // ReSharper disable CommentTypo // ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault +#nullable enable namespace CollapseLauncher; using KeybindAction = TypedEventHandler; @@ -172,7 +173,7 @@ private void RefreshPage_Invoked(KeyboardAccelerator sender, KeyboardAccelerator if (CannotUseKbShortcuts || !IsLoadRegionComplete) return; - if (!TryGetCurrentPageObject(out object typeOfPageObj)) + if (!TryGetCurrentPageObject(out object? typeOfPageObj)) { return; } @@ -198,9 +199,14 @@ private void RefreshPage_Invoked(KeyboardAccelerator sender, KeyboardAccelerator private void RestoreCurrentRegion() { - string gameTitle = GetAppConfigValue("GameCategory"); + string? gameTitle = GetAppConfigValue("GameCategory"); List gameTitleList = MetadataHelper.CurrentGameTitleList; + if (gameTitle == null) + { + return; + } + int indexCategory = gameTitleList.IndexOf(gameTitle); if (indexCategory < 0) indexCategory = 0; @@ -309,14 +315,14 @@ private void OpenPluginManager_Invoked(KeyboardAccelerator sender, KeyboardAccel _ = overlayMenu.ShowAsync(); } - private string GameDirPath => CurrentGameProperty.GameVersion?.GameDirPath!; + private string? GameDirPath => CurrentGameProperty?.GameVersion?.GameDirPath; private void OpenScreenshot_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) { if (!IsGameInstalled()) return; - string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty.GameVersion?.GamePreset.GameType switch + string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty?.GameVersion?.GamePreset.GameType switch { - GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName)}_Data\\ScreenShots", + GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty?.GameVersion?.GamePreset.GameExecutableName)}_Data\\ScreenShots", _ => "ScreenShot" }); @@ -368,10 +374,10 @@ private async void OpenGameCacheFolder_Invoked(KeyboardAccelerator sender, Keybo { if (!IsGameInstalled()) return; - string gameFolder = CurrentGameProperty.GameVersion?.GameDirAppDataPath ?? - CurrentGameProperty.GameVersion?.GameDirPath ?? - null; - + string? gameFolder = CurrentGameProperty?.GameVersion?.GameDirAppDataPath ?? + CurrentGameProperty?.GameVersion?.GameDirPath ?? + null; + if (string.IsNullOrEmpty(gameFolder)) return; LogWriteLine($"Opening Game Folder:\r\n\t{gameFolder}"); await Task.Run(() => @@ -394,7 +400,7 @@ await Task.Run(() => private void ForceCloseGame_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) { - if (!CurrentGameProperty.IsGameRunning) return; + if (!(CurrentGameProperty?.IsGameRunning ?? false)) return; PresetConfig gamePreset = CurrentGameProperty.GameVersion?.GamePreset!; string? gamePresetExecName = gamePreset.GameExecutableName; @@ -438,7 +444,7 @@ private void GoGameSettings_Invoked(KeyboardAccelerator sender, KeyboardAccelera if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; - Type? typeOfPage = CurrentGameProperty.GamePreset.GameType switch + Type? typeOfPage = CurrentGameProperty?.GamePreset.GameType switch { GameNameType.Honkai => typeof(HonkaiGameSettingsPage), GameNameType.Genshin => typeof(GenshinGameSettingsPage), diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs index fa03a0dfaa..fb89139045 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs @@ -4,16 +4,17 @@ using CollapseLauncher.Helper.Metadata; using CollapseLauncher.Interfaces; using CollapseLauncher.Pages; +using CollapseLauncher.Plugins; using CommunityToolkit.WinUI; using Hi3Helper; using Microsoft.UI; +using Microsoft.UI.Dispatching; using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -23,6 +24,7 @@ using static CollapseLauncher.InnerLauncherConfig; using static CollapseLauncher.Statics.GamePropertyVault; using static Hi3Helper.Logger; +using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions; // ReSharper disable CheckNamespace // ReSharper disable RedundantExtendsListEntry @@ -40,71 +42,162 @@ namespace CollapseLauncher; file static class NavigationExtension { - public static void Add(this IList list, string localePropertyPath, string? iconGlyph = null, object? tag = null) + public static (TItem Item, FontIcon? Icon) Add(this IList list, string localePropertyPath, string? iconGlyph = null, object? tag = null) where TItem : NavigationViewItemBase, new() { TItem item = new() { Tag = tag }; item.BindNavigationViewItemText(Locale.Current, localePropertyPath); + FontIcon? icon = null; if (item is NavigationViewItem asItem && iconGlyph != null) { - asItem.Icon = new FontIcon { Glyph = iconGlyph }; + asItem.Icon = icon = new FontIcon { Glyph = iconGlyph }; } list.Add(item); + return (item, icon); } - public static void Add(this IList list, string localePropertyPath, string? iconGlyph = null) + public static (TItem Item, FontIcon? Icon) Add(this IList list, string localePropertyPath, string? iconGlyph = null) where TItem : NavigationViewItemBase, new() where TPage : notnull { Type navigationType = typeof(TPage); - list.Add(localePropertyPath, iconGlyph, navigationType); + return list.Add(localePropertyPath, iconGlyph, navigationType); + } +} + +public class NavigationViewItemsContext +{ + public (NavigationViewItemBase Item, FontIcon Icon) HomePage { get; set; } + public (NavigationViewItemBase Item, FontIcon Icon) RepairPage { get; set; } + public (NavigationViewItemBase Item, FontIcon Icon) CachePage { get; set; } + public (NavigationViewItemBase Item, FontIcon Icon) GameSettingsPage { get; set; } + public (NavigationViewItemBase Item, FontIcon Icon) FileCleanupPage { get; set; } + + public static NavigationViewItemsContext Create(NavigationView navView) + { + foreach (FrameworkElement dependency in navView + .FindDescendants() + .OfType()) + { + // Avoid any icons to have shadow attached if it's not from this page + if (dependency.BaseUri.AbsolutePath != navView.BaseUri.AbsolutePath) + { + continue; + } + + if (dependency is IconElement icon) + { + MainPage.AttachShadowNavigationPanelItem(icon); + } + + if (dependency.Name == "PaneToggleButtonGrid" && + dependency is Grid paneToggleButtonGrid && + paneToggleButtonGrid.Children.FirstOrDefault(x => (x as Grid)?.Name == "ButtonHolderGrid") is Grid buttonHolderGrid) + { + foreach (Button button in buttonHolderGrid.Children.OfType - diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml.cs index 909388bfe1..ae37036866 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml.cs @@ -9,10 +9,10 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; +using WinRT; +using static CollapseLauncher.Statics.GamePropertyVault; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; -using static CollapseLauncher.Statics.GamePropertyVault; -using WinRT; #if !DISABLEDISCORD using CollapseLauncher.DiscordPresence; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs index 52e896f36d..3588ef5ca2 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.Ext.cs @@ -2,38 +2,29 @@ using Hi3Helper; using Hi3Helper.Win32.Screen; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; using System; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Drawing; -using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming -// ReSharper disable CommentTypo // ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +#pragma warning disable IDE0130 +#nullable enable namespace CollapseLauncher.Pages { - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public partial class StarRailGameSettingsPage : INotifyPropertyChanged + public partial class StarRailGameSettingsPage { - #region Methods - public event PropertyChangedEventHandler PropertyChanged = delegate { }; - // ReSharper disable once UnusedMember.Local - private void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - // Raise the PropertyChanged event, passing the name of the property whose value has changed. - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + #region Fields + private StarRailSettings? SettingsThis { get => field ??= Settings as StarRailSettings; } #endregion - + #region GameResolution public bool IsFullscreenEnabled { - get => ((StarRailSettings)Settings).SettingsScreen.isfullScreen; + get => SettingsThis?.SettingsScreen.isfullScreen ?? false; set { - ((StarRailSettings)Settings).SettingsScreen.isfullScreen = value; + SettingsThis?.SettingsScreen.isfullScreen = value; if (value) { GameWindowResizable.IsEnabled = false; @@ -51,10 +42,10 @@ public bool IsFullscreenEnabled public bool IsBorderlessEnabled { - get => ((StarRailSettings)Settings).SettingsCollapseScreen.UseBorderlessScreen; + get => SettingsThis?.SettingsCollapseScreen.UseBorderlessScreen ?? false; set { - ((StarRailSettings)Settings).SettingsCollapseScreen.UseBorderlessScreen = value; + Settings?.SettingsCollapseScreen.UseBorderlessScreen = value; if (value) { GameWindowResizable.IsEnabled = false; @@ -72,10 +63,10 @@ public bool IsBorderlessEnabled public bool IsCustomResolutionEnabled { - get => ((StarRailSettings)Settings).SettingsCollapseScreen.UseCustomResolution; + get => Settings?.SettingsCollapseScreen.UseCustomResolution ?? false; set { - ((StarRailSettings)Settings).SettingsCollapseScreen.UseCustomResolution = value; + Settings?.SettingsCollapseScreen.UseCustomResolution = value; if (value) { GameResolutionFullscreenExclusive.IsEnabled = false; @@ -108,11 +99,11 @@ public bool IsExclusiveFullscreenEnabled { get { - return IsFullscreenEnabled && ((StarRailSettings)Settings).SettingsCollapseScreen.UseExclusiveFullscreen; + return IsFullscreenEnabled && (Settings?.SettingsCollapseScreen.UseExclusiveFullscreen ?? false); } set { - ((StarRailSettings)Settings).SettingsCollapseScreen.UseExclusiveFullscreen = value; + Settings?.SettingsCollapseScreen.UseExclusiveFullscreen = value; if (value) { GameCustomResolutionCheckbox.IsEnabled = false; @@ -127,15 +118,15 @@ public bool IsExclusiveFullscreenEnabled public bool IsCanResizableWindow { - get => !((StarRailSettings)Settings).SettingsScreen.isfullScreen && !IsExclusiveFullscreenEnabled; + get => !(Settings?.SettingsScreen.isfullScreen ?? false) && !IsExclusiveFullscreenEnabled; } public bool IsResizableWindow { - get => ((StarRailSettings)Settings).SettingsCollapseScreen.UseResizableWindow; + get => Settings?.SettingsCollapseScreen.UseResizableWindow ?? false; set { - ((StarRailSettings)Settings).SettingsCollapseScreen.UseResizableWindow = value; + Settings?.SettingsCollapseScreen.UseResizableWindow = value; if (value) { GameCustomResolutionCheckbox.IsEnabled = true; @@ -150,19 +141,19 @@ public bool IsResizableWindow public bool IsCanCustomResolution { - get => ((StarRailSettings)Settings).SettingsCollapseScreen.UseResizableWindow && !IsExclusiveFullscreenEnabled; + get => (Settings?.SettingsCollapseScreen.UseResizableWindow ?? false) && !IsExclusiveFullscreenEnabled; } public int ResolutionW { - get => ((StarRailSettings)Settings).SettingsScreen.sizeRes.Width; - set => ((StarRailSettings)Settings).SettingsScreen.sizeRes = new Size(value, ResolutionH); + get => Settings?.SettingsScreen.sizeRes.Width ?? 0; + set => Settings?.SettingsScreen.sizeRes = new Size(value, ResolutionH); } public int ResolutionH { - get => ((StarRailSettings)Settings).SettingsScreen.sizeRes.Height; - set => ((StarRailSettings)Settings).SettingsScreen.sizeRes = new Size(ResolutionW, value); + get => Settings?.SettingsScreen.sizeRes.Height ?? 0; + set => Settings?.SettingsScreen.sizeRes = new Size(ResolutionW, value); } public bool IsCanResolutionWH @@ -174,7 +165,7 @@ public string ResolutionSelected { get { - string res = ((StarRailSettings)Settings).SettingsScreen.sizeResString; + string? res = Settings?.SettingsScreen.sizeResString; if (!string.IsNullOrEmpty(res)) { return res; @@ -183,7 +174,7 @@ public string ResolutionSelected Size size = ScreenProp.CurrentResolution; return $"{size.Width}x{size.Height}"; } - set => ((StarRailSettings)Settings).SettingsScreen.sizeResString = value; + set => Settings?.SettingsScreen.sizeResString = value; } #endregion @@ -193,7 +184,7 @@ public int FPS { get { - int value = Model.FpsIndexDict[NormalizeFPSNumber(((StarRailSettings)Settings).GraphicsSettings.FPS)]; + int value = Model.FpsIndexDict[NormalizeFPSNumber(SettingsThis?.GraphicsSettings.FPS ?? 0)]; if (value != 2) { return value; @@ -213,7 +204,7 @@ public int FPS } else { VSyncToggle.IsEnabled = true; } - ((StarRailSettings)Settings).GraphicsSettings.FPS = Model.FpsIndex[value]; + SettingsThis?.GraphicsSettings.FPS = Model.FpsIndex[value]; } } @@ -223,87 +214,87 @@ public int FPS //VSync public bool EnableVSync { - get => ((StarRailSettings)Settings).GraphicsSettings.EnableVSync; - set => ((StarRailSettings)Settings).GraphicsSettings.EnableVSync = value; + get => SettingsThis?.GraphicsSettings.EnableVSync ?? false; + set => SettingsThis?.GraphicsSettings.EnableVSync = value; } //RenderScale public double RenderScale { - get => Math.Round(((StarRailSettings)Settings).GraphicsSettings.RenderScale, 1); - set => ((StarRailSettings)Settings).GraphicsSettings.RenderScale = Math.Round(value, 1); // Round it to x.x (0.1) to fix floating-point rounding issue + get => Math.Round(SettingsThis?.GraphicsSettings.RenderScale ?? 0, 1); + set => SettingsThis?.GraphicsSettings.RenderScale = Math.Round(value, 1); // Round it to x.x (0.1) to fix floating-point rounding issue } //ResolutionQuality public int ResolutionQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.ResolutionQuality, 0, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.ResolutionQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.ResolutionQuality ?? 0), 0, 5); + set => SettingsThis?.GraphicsSettings.ResolutionQuality = (Quality)value; } //ShadowQuality public int ShadowQuality { get { - var v = Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.ShadowQuality, 0, 4); + int v = Math.Clamp((int)(SettingsThis?.GraphicsSettings.ShadowQuality ?? 0), 0, 4); if (v == 1) v = 2; return v; } - set => ((StarRailSettings)Settings).GraphicsSettings.ShadowQuality = (Quality)value; + set => SettingsThis?.GraphicsSettings.ShadowQuality = (Quality)value; } //LightQuality public int LightQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.LightQuality, 1, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.LightQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.LightQuality ?? 0), 1, 5); + set => SettingsThis?.GraphicsSettings.LightQuality = (Quality)value; } //CharacterQuality public int CharacterQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.CharacterQuality, 2, 4); - set => ((StarRailSettings)Settings).GraphicsSettings.CharacterQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.CharacterQuality ?? 0), 2, 4); + set => SettingsThis?.GraphicsSettings.CharacterQuality = (Quality)value; } //EnvDetailQuality public int EnvDetailQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.EnvDetailQuality, 1, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.EnvDetailQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.EnvDetailQuality ?? 0), 1, 5); + set => SettingsThis?.GraphicsSettings.EnvDetailQuality = (Quality)value; } //ReflectionQuality public int ReflectionQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.ReflectionQuality, 1, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.ReflectionQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.ReflectionQuality ?? 0), 1, 5); + set => SettingsThis?.GraphicsSettings.ReflectionQuality = (Quality)value; } //SFXQuality public int SFXQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.SFXQuality, 1, 4); - set => ((StarRailSettings)Settings).GraphicsSettings.SFXQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.SFXQuality ?? 0), 1, 4); + set => SettingsThis?.GraphicsSettings.SFXQuality = (Quality)value; } //DLSSQuality public int DlssQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.DlssQuality, 0, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.DlssQuality = (DLSSMode)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.DlssQuality ?? 0), 0, 5); + set => SettingsThis?.GraphicsSettings.DlssQuality = (DLSSMode)value; } //BloomQuality public int BloomQuality { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.BloomQuality, 0, 5); - set => ((StarRailSettings)Settings).GraphicsSettings.BloomQuality = (Quality)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.BloomQuality ?? 0), 0, 5); + set => SettingsThis?.GraphicsSettings.BloomQuality = (Quality)value; } //AAMode public int AAMode { - get => Math.Clamp((int)((StarRailSettings)Settings).GraphicsSettings.AAMode, 0, 2); - set => ((StarRailSettings)Settings).GraphicsSettings.AAMode = (AntialiasingMode)value; + get => Math.Clamp((int)(SettingsThis?.GraphicsSettings.AAMode ?? 0), 0, 2); + set => SettingsThis?.GraphicsSettings.AAMode = (AntialiasingMode)value; } //EnableSelfShadow public bool SelfShadow { get { - var v = ((StarRailSettings)Settings).GraphicsSettings.EnableSelfShadow; + int? v = SettingsThis?.GraphicsSettings.EnableSelfShadow; switch (v) { case 1: @@ -315,65 +306,65 @@ public bool SelfShadow return false; } } - set => ((StarRailSettings)Settings).GraphicsSettings.EnableSelfShadow = value ? 1 : 2; + set => SettingsThis?.GraphicsSettings.EnableSelfShadow = value ? 1 : 2; } //HalfResTransparent public bool HalfResTransparent { - get => ((StarRailSettings)Settings).GraphicsSettings.EnableHalfResTransparent; - set => ((StarRailSettings)Settings).GraphicsSettings.EnableHalfResTransparent = value; + get => SettingsThis?.GraphicsSettings.EnableHalfResTransparent ?? false; + set => SettingsThis?.GraphicsSettings.EnableHalfResTransparent = value; } #endregion #region Audio public int AudioMasterVolume { - get => ((StarRailSettings)Settings).AudioSettingsMaster.MasterVol = ((StarRailSettings)Settings).AudioSettingsMaster.MasterVol; - set => ((StarRailSettings)Settings).AudioSettingsMaster.MasterVol = value; + get => SettingsThis?.AudioSettingsMaster.MasterVol ?? 0; + set => SettingsThis?.AudioSettingsMaster.MasterVol = value; } public int AudioBGMVolume { - get => ((StarRailSettings)Settings).AudioSettingsBgm.BGMVol = ((StarRailSettings)Settings).AudioSettingsBgm.BGMVol; - set => ((StarRailSettings)Settings).AudioSettingsBgm.BGMVol = value; + get => SettingsThis?.AudioSettingsBgm.BGMVol ?? 0; + set => SettingsThis?.AudioSettingsBgm.BGMVol = value; } public int AudioSFXVolume { - get => ((StarRailSettings)Settings).AudioSettingsSfx.SFXVol = ((StarRailSettings)Settings).AudioSettingsSfx.SFXVol; - set => ((StarRailSettings)Settings).AudioSettingsSfx.SFXVol = value; + get => SettingsThis?.AudioSettingsSfx.SFXVol ?? 0; + set => SettingsThis?.AudioSettingsSfx.SFXVol = value; } public int AudioVOVolume { - get => ((StarRailSettings)Settings).AudioSettingsVo.VOVol = ((StarRailSettings)Settings).AudioSettingsVo.VOVol; - set => ((StarRailSettings)Settings).AudioSettingsVo.VOVol = value; + get => SettingsThis?.AudioSettingsVo.VOVol ?? 0; + set => SettingsThis?.AudioSettingsVo.VOVol = value; } public int AudioLang { - get => ((StarRailSettings)Settings).AudioLanguage.LocalAudioLangInt = ((StarRailSettings)Settings).AudioLanguage.LocalAudioLangInt; - set => ((StarRailSettings)Settings).AudioLanguage.LocalAudioLangInt = value; + get => SettingsThis?.AudioLanguage.LocalAudioLangInt ?? 0; + set => SettingsThis?.AudioLanguage.LocalAudioLangInt = value; } public int TextLang { - get => ((StarRailSettings)Settings).TextLanguage.LocalTextLangInt = ((StarRailSettings)Settings).TextLanguage.LocalTextLangInt; - set => ((StarRailSettings)Settings).TextLanguage.LocalTextLangInt = value; + get => SettingsThis?.TextLanguage.LocalTextLangInt ?? 0; + set => SettingsThis?.TextLanguage.LocalTextLangInt = value; } #endregion #region Misc public bool IsGameBoost { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.UseGameBoost; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.UseGameBoost = value; + get => SettingsThis?.SettingsCollapseMisc.UseGameBoost ?? false; + set => SettingsThis?.SettingsCollapseMisc.UseGameBoost = value; } public bool IsMobileMode { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.LaunchMobileMode; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.LaunchMobileMode = value; + get => SettingsThis?.SettingsCollapseMisc.LaunchMobileMode ?? false; + set => SettingsThis?.SettingsCollapseMisc.LaunchMobileMode = value; } #endregion @@ -382,13 +373,13 @@ public bool IsUseAdvancedSettings { get { - bool value = ((StarRailSettings)Settings).SettingsCollapseMisc.UseAdvancedGameSettings; + bool value = SettingsThis?.SettingsCollapseMisc.UseAdvancedGameSettings ?? false; AdvancedSettingsPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed; return value; } set { - ((StarRailSettings)Settings).SettingsCollapseMisc.UseAdvancedGameSettings = value; + Settings?.SettingsCollapseMisc.UseAdvancedGameSettings = value; AdvancedSettingsPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed; } } @@ -397,7 +388,7 @@ public bool IsUsePreLaunchCommand { get { - bool value = ((StarRailSettings)Settings).SettingsCollapseMisc.UseGamePreLaunchCommand; + bool value = SettingsThis?.SettingsCollapseMisc.UseGamePreLaunchCommand ?? false; if (value) { @@ -427,60 +418,53 @@ public bool IsUsePreLaunchCommand GameLaunchDelay.IsEnabled = false; } - ((StarRailSettings)Settings).SettingsCollapseMisc.UseGamePreLaunchCommand = value; + Settings?.SettingsCollapseMisc.UseGamePreLaunchCommand = value; } } public string PreLaunchCommand { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePreLaunchCommand; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePreLaunchCommand = value; + get => SettingsThis?.SettingsCollapseMisc.GamePreLaunchCommand ?? string.Empty; + set => Settings?.SettingsCollapseMisc.GamePreLaunchCommand = value; } public bool IsPreLaunchCommandExitOnGameClose { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePreLaunchExitOnGameStop; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePreLaunchExitOnGameStop = value; + get => SettingsThis?.SettingsCollapseMisc.GamePreLaunchExitOnGameStop ?? false; + set => Settings?.SettingsCollapseMisc.GamePreLaunchExitOnGameStop = value; } public int LaunchDelay { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.GameLaunchDelay; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.GameLaunchDelay = value; + get => SettingsThis?.SettingsCollapseMisc.GameLaunchDelay ?? 0; + set => Settings?.SettingsCollapseMisc.GameLaunchDelay = value; } public bool IsUsePostExitCommand { get { - bool value = ((StarRailSettings)Settings).SettingsCollapseMisc.UseGamePostExitCommand; + bool value = SettingsThis?.SettingsCollapseMisc.UseGamePostExitCommand ?? false; PostExitCommandTextBox.IsEnabled = value; return value; } set { PostExitCommandTextBox.IsEnabled = value; - ((StarRailSettings)Settings).SettingsCollapseMisc.UseGamePostExitCommand = value; + Settings?.SettingsCollapseMisc.UseGamePostExitCommand = value; } } public string PostExitCommand { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePostExitCommand; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.GamePostExitCommand = value; - } - - private void GameLaunchDelay_OnValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) - { - // clamp for negative value when clearing the number box - if ((int)sender.Value < 0) - sender.Value = 0; + get => SettingsThis?.SettingsCollapseMisc.GamePostExitCommand ?? string.Empty; + set => SettingsThis?.SettingsCollapseMisc.GamePostExitCommand = value; } public bool RunWithExplorerAsParent { - get => ((StarRailSettings)Settings).SettingsCollapseMisc.RunWithExplorerAsParent; - set => ((StarRailSettings)Settings).SettingsCollapseMisc.RunWithExplorerAsParent = value; + get => SettingsThis?.SettingsCollapseMisc.RunWithExplorerAsParent ?? false; + set => SettingsThis?.SettingsCollapseMisc.RunWithExplorerAsParent = value; } #endregion } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml index 716480b225..2c2dd57946 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml @@ -877,15 +877,6 @@ Text="{x:Bind helper:Locale.Current.Lang._GameSettingsPage.ApplyBtn, Mode=OneWay}" /> - diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml.cs index 2729d20738..e03af741b3 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/StarRailGameSettingsPage.xaml.cs @@ -1,6 +1,6 @@ using CollapseLauncher.Dialogs; -using CollapseLauncher.Helper; using CollapseLauncher.GameManagement.ImageBackground; +using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; using Hi3Helper; using Hi3Helper.Shared.ClassStruct; @@ -10,9 +10,9 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; +using static CollapseLauncher.Statics.GamePropertyVault; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; -using static CollapseLauncher.Statics.GamePropertyVault; #if !DISABLEDISCORD using CollapseLauncher.DiscordPresence; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.Ext.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.Ext.cs index da45759890..71af22fb83 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.Ext.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.Ext.cs @@ -7,29 +7,22 @@ using Microsoft.UI.Xaml.Controls; using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Drawing; -using System.Runtime.CompilerServices; -// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault -// ReSharper disable CommentTypo - // ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +#pragma warning disable IDE0130 +#nullable enable namespace CollapseLauncher.Pages { - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public partial class ZenlessGameSettingsPage : INotifyPropertyChanged + public partial class ZenlessGameSettingsPage { - #region Methods - public event PropertyChangedEventHandler PropertyChanged = delegate { }; - // ReSharper disable once UnusedMember.Local - private void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - // Raise the PropertyChanged event, passing the name of the property whose value has changed. - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + #region Fields + private ZenlessSettings? SettingsThis { get => field ??= Settings as ZenlessSettings; } + #endregion + #region Methods private GraphicsPresetOption? oldPreset; private void PresetSelector(object sender, SelectionChangedEventArgs _) { @@ -135,10 +128,10 @@ private void EnforceCustomPreset_ComboBox(object _, SelectionChangedEventArgs n) public bool IsFullscreenEnabled { - get => ((ZenlessSettings)Settings).SettingsScreen.isfullScreen; + get => SettingsThis?.SettingsScreen.isfullScreen ?? false; set { - ((ZenlessSettings)Settings).SettingsScreen.isfullScreen = value; + SettingsThis?.SettingsScreen.isfullScreen = value; if (value) { GameWindowResizable.IsEnabled = false; @@ -157,10 +150,10 @@ public bool IsFullscreenEnabled public bool IsBorderlessEnabled { - get => ((ZenlessSettings)Settings).SettingsCollapseScreen.UseBorderlessScreen; + get => SettingsThis?.SettingsCollapseScreen.UseBorderlessScreen ?? false; set { - ((ZenlessSettings)Settings).SettingsCollapseScreen.UseBorderlessScreen = value; + SettingsThis?.SettingsCollapseScreen.UseBorderlessScreen = value; if (value) { GameWindowResizable.IsEnabled = false; @@ -178,10 +171,10 @@ public bool IsBorderlessEnabled public bool IsCustomResolutionEnabled { - get => ((ZenlessSettings)Settings).SettingsCollapseScreen.UseCustomResolution; + get => SettingsThis?.SettingsCollapseScreen.UseCustomResolution ?? false; set { - ((ZenlessSettings)Settings).SettingsCollapseScreen.UseCustomResolution = value; + SettingsThis?.SettingsCollapseScreen.UseCustomResolution = value; if (value) { GameResolutionFullscreenExclusive.IsEnabled = false; @@ -212,10 +205,10 @@ public bool IsCustomResolutionEnabled public bool IsExclusiveFullscreenEnabled { - get => IsFullscreenEnabled && ((ZenlessSettings)Settings).SettingsCollapseScreen.UseExclusiveFullscreen; + get => IsFullscreenEnabled && (SettingsThis?.SettingsCollapseScreen.UseExclusiveFullscreen ?? false); set { - ((ZenlessSettings)Settings).SettingsCollapseScreen.UseExclusiveFullscreen = value; + SettingsThis?.SettingsCollapseScreen.UseExclusiveFullscreen = value; if (value) { GameCustomResolutionCheckbox.IsEnabled = false; @@ -228,14 +221,14 @@ public bool IsExclusiveFullscreenEnabled } } - public bool IsCanResizableWindow => !((ZenlessSettings)Settings).SettingsScreen.isfullScreen && !IsExclusiveFullscreenEnabled; + public bool IsCanResizableWindow => !(SettingsThis?.SettingsScreen.isfullScreen ?? false) && !IsExclusiveFullscreenEnabled; public bool IsResizableWindow { - get => ((ZenlessSettings)Settings).SettingsCollapseScreen.UseResizableWindow; + get => SettingsThis?.SettingsCollapseScreen.UseResizableWindow ?? false; set { - ((ZenlessSettings)Settings).SettingsCollapseScreen.UseResizableWindow = value; + SettingsThis?.SettingsCollapseScreen.UseResizableWindow = value; if (value) { GameCustomResolutionCheckbox.IsEnabled = true; @@ -248,18 +241,18 @@ public bool IsResizableWindow } } - public bool IsCanCustomResolution => ((ZenlessSettings)Settings).SettingsCollapseScreen.UseResizableWindow && !IsExclusiveFullscreenEnabled; + public bool IsCanCustomResolution => (SettingsThis?.SettingsCollapseScreen.UseResizableWindow ?? false) && !IsExclusiveFullscreenEnabled; public int ResolutionW { - get => ((ZenlessSettings)Settings).SettingsScreen.sizeRes.Width; - set => ((ZenlessSettings)Settings).SettingsScreen.sizeRes = new Size(value, ResolutionH); + get => SettingsThis?.SettingsScreen.sizeRes.Width ?? 0; + set => SettingsThis?.SettingsScreen.sizeRes = new Size(value, ResolutionH); } public int ResolutionH { - get => ((ZenlessSettings)Settings).SettingsScreen.sizeRes.Height; - set => ((ZenlessSettings)Settings).SettingsScreen.sizeRes = new Size(ResolutionW, value); + get => SettingsThis?.SettingsScreen.sizeRes.Height ?? 0; + set => SettingsThis?.SettingsScreen.sizeRes = new Size(ResolutionW, value); } public bool IsCanResolutionWH => IsCustomResolutionEnabled; @@ -268,7 +261,7 @@ internal string ResolutionSelected { get { - string res = ((ZenlessSettings)Settings).SettingsScreen.sizeResString; + string? res = SettingsThis?.SettingsScreen.sizeResString; if (!string.IsNullOrEmpty(res)) { return res; @@ -292,7 +285,7 @@ public int ResolutionIndexSelected // Get index of the resolution and clamp it to the valid index if possible (to default as 0). // [Added from @bagusnl docs] Usually, the game will use -1 as its arbitrary value (SMH) - int res = ((ZenlessSettings)Settings).GeneralData?.ResolutionIndex ?? 0; + int res = SettingsThis?.GeneralData?.ResolutionIndex ?? 0; int indexRes = ScreenResolutionIsFullscreenIdx.Count < res || res < 0 ? 0 : res; // Get the value from Fullscreen index of the resolution @@ -320,7 +313,7 @@ public int ResolutionIndexSelected IsFullscreenEnabled = isFullscreen; // Set the resolution index - ((ZenlessSettings)Settings).GeneralData.ResolutionIndex = value; + SettingsThis?.GeneralData.ResolutionIndex = value; } } #endregion @@ -328,17 +321,17 @@ public int ResolutionIndexSelected #region Misc public bool IsGameBoost { - get => Settings?.SettingsCollapseMisc?.UseGameBoost ?? false; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.UseGameBoost = value; + get => SettingsThis?.SettingsCollapseMisc?.UseGameBoost ?? false; + set => SettingsThis?.SettingsCollapseMisc.UseGameBoost = value; } public bool IsMobileMode { - get => Settings?.SettingsCollapseMisc?.LaunchMobileMode ?? false; + get => SettingsThis?.SettingsCollapseMisc?.LaunchMobileMode ?? false; set { - ((ZenlessSettings)Settings).SettingsCollapseMisc.LaunchMobileMode = value; - ((ZenlessSettings)Settings).GeneralData.LocalUILayoutPlatform = + SettingsThis?.SettingsCollapseMisc.LaunchMobileMode = value; + SettingsThis?.GeneralData.LocalUILayoutPlatform = value ? LocalUiLayoutPlatform.Mobile : LocalUiLayoutPlatform.PC; } @@ -350,13 +343,13 @@ public bool IsUseAdvancedSettings { get { - bool value = Settings?.SettingsCollapseMisc?.UseAdvancedGameSettings ?? false; + bool value = SettingsThis?.SettingsCollapseMisc?.UseAdvancedGameSettings ?? false; AdvancedSettingsPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed; return value; } set { - ((ZenlessSettings)Settings).SettingsCollapseMisc.UseAdvancedGameSettings = value; + SettingsThis?.SettingsCollapseMisc.UseAdvancedGameSettings = value; AdvancedSettingsPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed; } } @@ -365,7 +358,7 @@ public bool IsUsePreLaunchCommand { get { - bool value = Settings?.SettingsCollapseMisc?.UseGamePreLaunchCommand ?? false; + bool value = SettingsThis?.SettingsCollapseMisc?.UseGamePreLaunchCommand ?? false; if (value) { @@ -395,60 +388,53 @@ public bool IsUsePreLaunchCommand GameLaunchDelay.IsEnabled = false; } - ((ZenlessSettings)Settings).SettingsCollapseMisc.UseGamePreLaunchCommand = value; + SettingsThis?.SettingsCollapseMisc.UseGamePreLaunchCommand = value; } } public string PreLaunchCommand { - get => Settings?.SettingsCollapseMisc?.GamePreLaunchCommand; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.GamePreLaunchCommand = value; + get => SettingsThis?.SettingsCollapseMisc?.GamePreLaunchCommand ?? string.Empty; + set => SettingsThis?.SettingsCollapseMisc.GamePreLaunchCommand = value; } public bool IsPreLaunchCommandExitOnGameClose { - get => Settings?.SettingsCollapseMisc?.GamePreLaunchExitOnGameStop ?? false; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.GamePreLaunchExitOnGameStop = value; + get => SettingsThis?.SettingsCollapseMisc?.GamePreLaunchExitOnGameStop ?? false; + set => SettingsThis?.SettingsCollapseMisc.GamePreLaunchExitOnGameStop = value; } public int LaunchDelay { - get => Settings?.SettingsCollapseMisc?.GameLaunchDelay ?? 0; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.GameLaunchDelay = value; + get => SettingsThis?.SettingsCollapseMisc?.GameLaunchDelay ?? 0; + set => SettingsThis?.SettingsCollapseMisc.GameLaunchDelay = value; } public bool IsUsePostExitCommand { get { - bool value = Settings?.SettingsCollapseMisc?.UseGamePostExitCommand ?? false; + bool value = SettingsThis?.SettingsCollapseMisc?.UseGamePostExitCommand ?? false; PostExitCommandTextBox.IsEnabled = value; return value; } set { PostExitCommandTextBox.IsEnabled = value; - ((ZenlessSettings)Settings).SettingsCollapseMisc.UseGamePostExitCommand = value; + SettingsThis?.SettingsCollapseMisc.UseGamePostExitCommand = value; } } public string PostExitCommand { - get => Settings?.SettingsCollapseMisc?.GamePostExitCommand; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.GamePostExitCommand = value; - } - - private void GameLaunchDelay_OnValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) - { - // clamp for negative value when clearing the number box - if ((int)sender.Value < 0) - sender.Value = 0; + get => SettingsThis?.SettingsCollapseMisc?.GamePostExitCommand ?? string.Empty; + set => SettingsThis?.SettingsCollapseMisc.GamePostExitCommand = value; } public bool RunWithExplorerAsParent { - get => ((ZenlessSettings)Settings).SettingsCollapseMisc.RunWithExplorerAsParent; - set => ((ZenlessSettings)Settings).SettingsCollapseMisc.RunWithExplorerAsParent = value; + get => SettingsThis?.SettingsCollapseMisc?.RunWithExplorerAsParent ?? false; + set => SettingsThis?.SettingsCollapseMisc.RunWithExplorerAsParent = value; } #endregion @@ -457,170 +443,172 @@ public int Lang_Text { get { - var v = (int)((ZenlessSettings)Settings).GeneralData.DeviceLanguageType; + int v = (int)(SettingsThis?.GeneralData.DeviceLanguageType ?? default); return v <= 0 ? 1 : v; } - set => ((ZenlessSettings)Settings).GeneralData.DeviceLanguageType = (LanguageText)value; + set => SettingsThis?.GeneralData.DeviceLanguageType = (LanguageText)value; } public int Lang_Audio { get { - var v = (int)((ZenlessSettings)Settings).GeneralData.DeviceLanguageVoiceType; + int v = (int)(SettingsThis?.GeneralData.DeviceLanguageVoiceType ?? default); return v <= 0 ? 1 : v; } - set => ((ZenlessSettings)Settings).GeneralData.DeviceLanguageVoiceType = (LanguageVoice)value; + set => SettingsThis?.GeneralData.DeviceLanguageVoiceType = (LanguageVoice)value; } #endregion #region Graphics Settings - GENERAL_DATA > SystemSettingDataMap public bool EnableVSync { - get => ((ZenlessSettings)Settings).GeneralData?.VSync ?? false; - set => ((ZenlessSettings)Settings).GeneralData.VSync = value; + get => SettingsThis?.GeneralData?.VSync ?? false; + set => SettingsThis?.GeneralData.VSync = value; } public int Graphics_Preset { - get => (int)((ZenlessSettings)Settings).GeneralData.GraphicsPreset; - set => ((ZenlessSettings)Settings).GeneralData.GraphicsPreset = (GraphicsPresetOption)value; + get => (int)(SettingsThis?.GeneralData.GraphicsPreset ?? default); + set => SettingsThis?.GeneralData.GraphicsPreset = (GraphicsPresetOption)value; } public int Graphics_RenderRes { - get => (int)((ZenlessSettings)Settings).GeneralData.RenderResolution; - set => ((ZenlessSettings)Settings).GeneralData.RenderResolution = (RenderResOption)value; + get => (int)(SettingsThis?.GeneralData.RenderResolution ?? default); + set => SettingsThis?.GeneralData.RenderResolution = (RenderResOption)value; } public int Graphics_Shadow { - get => (int)((ZenlessSettings)Settings).GeneralData.ShadowQuality; - set => ((ZenlessSettings)Settings).GeneralData.ShadowQuality = (QualityOption3)value; + get => (int)(SettingsThis?.GeneralData.ShadowQuality ?? default); + set => SettingsThis?.GeneralData.ShadowQuality = (QualityOption3)value; } public int Graphics_AntiAliasing { - get => (int)((ZenlessSettings)Settings).GeneralData.AntiAliasing; - set => ((ZenlessSettings)Settings).GeneralData.AntiAliasing = (AntiAliasingOption)value; + get => (int)(SettingsThis?.GeneralData.AntiAliasing ?? default); + set => SettingsThis?.GeneralData.AntiAliasing = (AntiAliasingOption)value; } public int Graphics_VolFog { - get => (int)((ZenlessSettings)Settings).GeneralData.VolumetricFogQuality; - set => ((ZenlessSettings)Settings).GeneralData.VolumetricFogQuality = (QualityOption4)value; + get => (int)(SettingsThis?.GeneralData.VolumetricFogQuality ?? default); + set => SettingsThis?.GeneralData.VolumetricFogQuality = (QualityOption4)value; } public bool Graphics_Bloom { - get => ((ZenlessSettings)Settings).GeneralData.Bloom; - set => ((ZenlessSettings)Settings).GeneralData.Bloom = value; + get => SettingsThis?.GeneralData.Bloom ?? false; + set => SettingsThis?.GeneralData.Bloom = value; } public bool Graphics_MotionBlur { - get => ((ZenlessSettings)Settings).GeneralData.MotionBlur; - set => ((ZenlessSettings)Settings).GeneralData.MotionBlur = value; + get => SettingsThis?.GeneralData.MotionBlur ?? false; + set => SettingsThis?.GeneralData.MotionBlur = value; } public int Graphics_Reflection { - get => (int)((ZenlessSettings)Settings).GeneralData.ReflectionQuality; - set => ((ZenlessSettings)Settings).GeneralData.ReflectionQuality = (QualityOption4)value; + get => (int)(SettingsThis?.GeneralData.ReflectionQuality ?? default); + set => SettingsThis?.GeneralData.ReflectionQuality = (QualityOption4)value; } public int Graphics_Effects { - get => (int)((ZenlessSettings)Settings).GeneralData.FxQuality; - set => ((ZenlessSettings)Settings).GeneralData.FxQuality = (QualityOption5)value; + get => (int)(SettingsThis?.GeneralData.FxQuality ?? default); + set => SettingsThis?.GeneralData.FxQuality = (QualityOption5)value; } public int Graphics_ColorFilter { - get => ((ZenlessSettings)Settings).GeneralData.ColorFilter; - set => ((ZenlessSettings)Settings).GeneralData.ColorFilter = value; + get => SettingsThis?.GeneralData.ColorFilter ?? 0; + set => SettingsThis?.GeneralData.ColorFilter = value; } public int Graphics_Character { - get => (int)((ZenlessSettings)Settings).GeneralData.CharacterQuality; - set => ((ZenlessSettings)Settings).GeneralData.CharacterQuality = (QualityOption2)value; + get => (int)(SettingsThis?.GeneralData.CharacterQuality ?? default); + set => SettingsThis?.GeneralData.CharacterQuality = (QualityOption2)value; } public bool Graphics_Distortion { - get => ((ZenlessSettings)Settings).GeneralData.Distortion; - set => ((ZenlessSettings)Settings).GeneralData.Distortion = value; + get => SettingsThis?.GeneralData.Distortion ?? false; + set => SettingsThis?.GeneralData.Distortion = value; } public int Graphics_Shading { - get => (int)((ZenlessSettings)Settings).GeneralData.ShadingQuality; - set => ((ZenlessSettings)Settings).GeneralData.ShadingQuality = (QualityOption3)value; + get => (int)(SettingsThis?.GeneralData.ShadingQuality ?? default); + set => SettingsThis?.GeneralData.ShadingQuality = (QualityOption3)value; } public int Graphics_Environment { - get => (int)((ZenlessSettings)Settings).GeneralData.EnvironmentQuality; - set => ((ZenlessSettings)Settings).GeneralData.EnvironmentQuality = (QualityOption2)value; + get => (int)(SettingsThis?.GeneralData.EnvironmentQuality ?? default); + set => SettingsThis?.GeneralData.EnvironmentQuality = (QualityOption2)value; } public int Graphics_AnisotropicSampling { - get => (int)((ZenlessSettings)Settings).GeneralData.AnisotropicSampling; - set => ((ZenlessSettings)Settings).GeneralData.AnisotropicSampling = (AnisotropicSamplingOption)value; + get => (int)(SettingsThis?.GeneralData.AnisotropicSampling ?? default); + set => SettingsThis?.GeneralData.AnisotropicSampling = (AnisotropicSamplingOption)value; } public int Graphics_GlobalIllumination { - get => (int)((ZenlessSettings)Settings).GeneralData.GlobalIllumination; - set => ((ZenlessSettings)Settings).GeneralData.GlobalIllumination = (QualityOption3)value; + get => (int)(SettingsThis?.GeneralData.GlobalIllumination ?? default); + set => SettingsThis?.GeneralData.GlobalIllumination = (QualityOption3)value; } public int Graphics_Fps { - get => (int)((ZenlessSettings)Settings).GeneralData.Fps; - set => ((ZenlessSettings)Settings).GeneralData.Fps = (FpsOption)value; + get => (int)(SettingsThis?.GeneralData.Fps ?? default); + set => SettingsThis?.GeneralData.Fps = (FpsOption)value; } - /// + /// public int Graphics_HiPreCharaAnim { - get => (int)((ZenlessSettings)Settings).GeneralData.HiPrecisionCharaAnim; - set => ((ZenlessSettings)Settings).GeneralData.HiPrecisionCharaAnim = (HiPrecisionCharaAnimOption)value; + get => (int)(SettingsThis?.GeneralData.HiPrecisionCharaAnim ?? default); + set => SettingsThis?.GeneralData.HiPrecisionCharaAnim = (HiPrecisionCharaAnimOption)value; } public bool AdvancedGraphics_UseDirectX12Api { - get => ((ZenlessSettings)Settings).SettingsCollapseScreen.GameGraphicsAPI == 4; - set => ((ZenlessSettings)Settings).SettingsCollapseScreen.GameGraphicsAPI = value ? 4 : 3; + get => SettingsThis?.SettingsCollapseScreen.GameGraphicsAPI == 4; + set => SettingsThis?.SettingsCollapseScreen.GameGraphicsAPI = value ? 4 : 3; } public bool AdvancedGraphics_UseRayTracing { - get => ((ZenlessSettings)Settings).GeneralData.RayTracing_Enabled; - set => ((ZenlessSettings)Settings).GeneralData.RayTracing_Enabled = value; + get => SettingsThis?.GeneralData.RayTracing_Enabled ?? false; + set => SettingsThis?.GeneralData.RayTracing_Enabled = value; } public int AdvancedGraphics_RayTracingQuality { - get => (int)((ZenlessSettings)Settings).GeneralData.RayTracing_Quality; - set => ((ZenlessSettings)Settings).GeneralData.RayTracing_Quality = (QualityOption3)value; + get => (int)(SettingsThis?.GeneralData.RayTracing_Quality ?? default); + set => SettingsThis?.GeneralData.RayTracing_Quality = (QualityOption3)value; } public int AdvancedGraphics_SuperResolutionOption { - get => (int)((ZenlessSettings)Settings).GeneralData.SuperResolution_Option; - set => ((ZenlessSettings)Settings).GeneralData.SuperResolution_Option = (SuperResolutionScalingOption)value; + get => (int)(SettingsThis?.GeneralData.SuperResolution_Option ?? default); + set => SettingsThis?.GeneralData.SuperResolution_Option = (SuperResolutionScalingOption)value; } public int AdvancedGraphics_SuperResolutionQuality { - get => (int)((ZenlessSettings)Settings).GeneralData.SuperResolution_Quality; - set => ((ZenlessSettings)Settings).GeneralData.SuperResolution_Quality = (SuperResolutionScalingQuality)value; + get => (int)(SettingsThis?.GeneralData.SuperResolution_Quality ?? default); + set => SettingsThis?.GeneralData.SuperResolution_Quality = (SuperResolutionScalingQuality)value; } + // ReSharper disable once IdentifierTypo private static bool? _isDeviceHasRTXGPU; + // ReSharper disable once IdentifierTypo public bool IsDeviceHasRTXGPU { get @@ -652,44 +640,44 @@ public bool IsDeviceHasRTXGPU public int Audio_VolMain { - get => ((ZenlessSettings)Settings).GeneralData.Audio_MainVolume; - set => ((ZenlessSettings)Settings).GeneralData.Audio_MainVolume = value; + get => SettingsThis?.GeneralData.Audio_MainVolume ?? 0; + set => SettingsThis?.GeneralData.Audio_MainVolume = value; } public int Audio_VolMusic { - get => ((ZenlessSettings)Settings).GeneralData.Audio_MusicVolume; - set => ((ZenlessSettings)Settings).GeneralData.Audio_MusicVolume = value; + get => SettingsThis?.GeneralData.Audio_MusicVolume ?? 0; + set => SettingsThis?.GeneralData.Audio_MusicVolume = value; } public int Audio_VolDialog { - get => ((ZenlessSettings)Settings).GeneralData.Audio_DialogVolume; - set => ((ZenlessSettings)Settings).GeneralData.Audio_DialogVolume = value; + get => SettingsThis?.GeneralData.Audio_DialogVolume ?? 0; + set => SettingsThis?.GeneralData.Audio_DialogVolume = value; } public int Audio_VolSfx { - get => ((ZenlessSettings)Settings).GeneralData.Audio_SfxVolume; - set => ((ZenlessSettings)Settings).GeneralData.Audio_SfxVolume = value; + get => SettingsThis?.GeneralData.Audio_SfxVolume ?? 0; + set => SettingsThis?.GeneralData.Audio_SfxVolume = value; } public int Audio_VolAmbient { - get => ((ZenlessSettings)Settings).GeneralData.Audio_AmbientVolume; - set => ((ZenlessSettings)Settings).GeneralData.Audio_AmbientVolume = value; + get => SettingsThis?.GeneralData.Audio_AmbientVolume ?? 0; + set => SettingsThis?.GeneralData.Audio_AmbientVolume = value; } public int Audio_PlaybackDevice { - get => (int)((ZenlessSettings)Settings).GeneralData.Audio_PlaybackDevice; - set => ((ZenlessSettings)Settings).GeneralData.Audio_PlaybackDevice = (AudioPlaybackDevice)value; + get => (int)(SettingsThis?.GeneralData.Audio_PlaybackDevice ?? default); + set => SettingsThis?.GeneralData.Audio_PlaybackDevice = (AudioPlaybackDevice)value; } public bool Audio_MuteOnMinimize { - get => ((ZenlessSettings)Settings).GeneralData.Audio_MuteOnMinimize; - set => ((ZenlessSettings)Settings).GeneralData.Audio_MuteOnMinimize = value; + get => SettingsThis?.GeneralData.Audio_MuteOnMinimize ?? false; + set => SettingsThis?.GeneralData.Audio_MuteOnMinimize = value; } #endregion } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml index ed2b85218d..ae82eaca00 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml @@ -1092,15 +1092,6 @@ Text="{x:Bind helper:Locale.Current.Lang._GameSettingsPage.ApplyBtn, Mode=OneWay}" /> - diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml.cs index ff1eefd479..a7854b1f58 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/ZenlessGameSettingsPage.xaml.cs @@ -6,10 +6,11 @@ using CollapseLauncher.Helper.Animation; using Hi3Helper; using Hi3Helper.Data; +using Hi3Helper.SentryHelper; using Hi3Helper.Shared.ClassStruct; +using Hi3Helper.Win32.Screen; using Microsoft.UI.Xaml; using Microsoft.Win32; -using Hi3Helper.Win32.Screen; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -18,9 +19,8 @@ using System.Linq; using System.Numerics; using System.Threading.Tasks; -using static Hi3Helper.Shared.Region.LauncherConfig; using static CollapseLauncher.Statics.GamePropertyVault; -using Hi3Helper.SentryHelper; +using static Hi3Helper.Shared.Region.LauncherConfig; // ReSharper disable CommentTypo // ReSharper disable StringLiteralTypo // ReSharper disable AsyncVoidMethod diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs index d51bf85b0d..126c7a41c3 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Background.cs @@ -1,6 +1,8 @@ using CollapseLauncher.Dialogs; using CollapseLauncher.GameManagement.ImageBackground; using CollapseLauncher.Helper; +using CollapseLauncher.Helper.LauncherApiLoader; +using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; using CollapseLauncher.Helper.Metadata; using CollapseLauncher.Statics; using CollapseLauncher.XAMLs.Theme.CustomControls; @@ -36,6 +38,28 @@ namespace CollapseLauncher.Pages; public sealed partial class HomePage { + public HypLauncherBackgroundList? CurrentGameBackgroundData + { + get + { + ILauncherApi? api = CurrentPresetConfig?.GameLauncherApi; + HypLauncherBackgroundList? data = api?.LauncherGameBackground?.Data; + return data; + } + } + + private void ClickImageEventSpriteLink(object sender, PointerRoutedEventArgs e) + { + if (sender is not FrameworkElement asUiElement || + !e.GetCurrentPoint(asUiElement).Properties.IsLeftButtonPressed || + asUiElement.Tag is not string url) + { + return; + } + + SpawnWebView2.SpawnWebView2Window(url, Content); + } + #region Background element events private CancellationTokenSource? _multiBackgroundGridHoverCts; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs index 2feede27a9..a9b47d07c6 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs @@ -12,6 +12,7 @@ using Hi3Helper.EncTool.WindowTool; using Hi3Helper.Plugin.Core.Utility; using Hi3Helper.SentryHelper; +using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Win32.ManagedTools; using Hi3Helper.Win32.Native.Enums; using Hi3Helper.Win32.Native.LibraryImport; @@ -155,16 +156,17 @@ private async void StartGame(object? sender, RoutedEventArgs? e) } GameRunningWatcher(_Settings); - - switch (GetAppConfigValue("GameLaunchedBehavior").ToString()) + GameLaunchedBehavior behavior = GetAppConfigValue("GameLaunchedBehavior").ToEnum(); + + switch (behavior) { - case "ToTray": + case GameLaunchedBehavior.ToTray: WindowUtility.ToggleToTray_MainWindow(); break; - case "Nothing": + case GameLaunchedBehavior.Nothing: break; // ReSharper disable once RedundantCaseLabel - case "Minimize": + case GameLaunchedBehavior.Minimize: default: WindowUtility.WindowMinimize(false); break; @@ -203,11 +205,11 @@ private async void StartGame(object? sender, RoutedEventArgs? e) private static int CreateProcessWithParent(string parent, string applicationPath, string arguments, string workingDirectory) { - var parentHandle = IntPtr.Zero; - var hToken = IntPtr.Zero; - var hDuplicateToken = IntPtr.Zero; - var attributeList = IntPtr.Zero; - var pHandle = IntPtr.Zero; + var parentHandle = nint.Zero; + var hToken = nint.Zero; + var hDuplicateToken = nint.Zero; + var attributeList = nint.Zero; + var pHandle = nint.Zero; try { @@ -217,13 +219,13 @@ private static int CreateProcessWithParent(string parent, string applicationPath return -1; const int PROCESS_CREATE_PROCESS = 0x0080; parentHandle = PInvoke.OpenProcess(PROCESS_CREATE_PROCESS, false, parentProc.Id); - if (parentHandle == IntPtr.Zero) + if (parentHandle == nint.Zero) return -1; // Obtain the current process token if (!PInvoke.OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ACCESS.TOKEN_ALL_ACCESS, out hToken)) return -1; - if (!PInvoke.DuplicateTokenEx(hToken, TOKEN_ACCESS.TOKEN_ALL_ACCESS, IntPtr.Zero, + if (!PInvoke.DuplicateTokenEx(hToken, TOKEN_ACCESS.TOKEN_ALL_ACCESS, nint.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hDuplicateToken)) return -1; @@ -231,21 +233,21 @@ private static int CreateProcessWithParent(string parent, string applicationPath // Initialize the attribute list with the parent process var siex = new STARTUPINFOEX(); siex.StartupInfo.cb = Marshal.SizeOf(); - var attributeSize = IntPtr.Zero; - PInvoke.InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref attributeSize); + var attributeSize = nint.Zero; + PInvoke.InitializeProcThreadAttributeList(nint.Zero, 1, 0, ref attributeSize); attributeList = Marshal.AllocHGlobal(attributeSize); if (!PInvoke.InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeSize)) return -1; - pHandle = Marshal.AllocHGlobal(IntPtr.Size); + pHandle = Marshal.AllocHGlobal(nint.Size); Marshal.WriteIntPtr(pHandle, parentHandle); - if (!PInvoke.UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE.PARENT_PROCESS, pHandle, IntPtr.Size, - IntPtr.Zero, IntPtr.Zero)) + if (!PInvoke.UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE.PARENT_PROCESS, pHandle, nint.Size, + nint.Zero, nint.Zero)) return -1; siex.lpAttributeList = attributeList; // Create the new process as a child of the specified parent process, and with our process user token - var ok = PInvoke.CreateProcessAsUser(hDuplicateToken, applicationPath, arguments, IntPtr.Zero, IntPtr.Zero, false, - ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, workingDirectory, ref siex, + var ok = PInvoke.CreateProcessAsUser(hDuplicateToken, applicationPath, arguments, nint.Zero, nint.Zero, false, + ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT, nint.Zero, workingDirectory, ref siex, out var pi); if (!ok) return -1; @@ -257,22 +259,22 @@ private static int CreateProcessWithParent(string parent, string applicationPath } finally { - if (hDuplicateToken != IntPtr.Zero) + if (hDuplicateToken != nint.Zero) PInvoke.CloseHandle(hDuplicateToken); - if (hToken != IntPtr.Zero) + if (hToken != nint.Zero) PInvoke.CloseHandle(hToken); - if (pHandle != IntPtr.Zero) + if (pHandle != nint.Zero) Marshal.FreeHGlobal(pHandle); - if (attributeList != IntPtr.Zero) + if (attributeList != nint.Zero) { PInvoke.DeleteProcThreadAttributeList(attributeList); Marshal.FreeHGlobal(attributeList); } - if (parentHandle != IntPtr.Zero) + if (parentHandle != nint.Zero) PInvoke.CloseHandle(parentHandle); } } @@ -512,7 +514,7 @@ private async void ReadOutputLog() #region Exclusive Window Payload private async void StartExclusiveWindowPayload() { - IntPtr _windowPtr = ProcessChecker.GetProcessWindowHandle(CurrentGameProperty.GameVersion?.GamePreset.GameExecutableName ?? ""); + nint _windowPtr = ProcessChecker.GetProcessWindowHandle(CurrentGameProperty.GameVersion?.GamePreset.GameExecutableName ?? ""); await Task.Delay(1000); Windowing.HideWindow(_windowPtr); await Task.Delay(1000); @@ -831,7 +833,7 @@ private async Task CheckRunningGameInstance(PresetConfig presetConfig, Cancellat #if !DISABLEDISCORD if (ToggleRegionPlayingRpc) - AppDiscordPresence?.SetActivity(ActivityType.Play, fromActivityOffset.ToUniversalTime()); + AppDiscordPresence.SetActivity(ActivityType.Play, fromActivityOffset.ToUniversalTime()); #endif int height = gameSettings.SettingsScreen?.height ?? 0; @@ -890,7 +892,7 @@ Task ProcessAwaiter(CancellationToken x) => PlaytimeRunningStack.Visibility = Visibility.Collapsed; #if !DISABLEDISCORD - AppDiscordPresence?.SetActivity(ActivityType.Idle); + AppDiscordPresence.SetActivity(ActivityType.Idle); #endif } catch (TaskCanceledException) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs index d5c1cef8a5..4aa2b04d08 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs @@ -55,24 +55,22 @@ private async void CollapsePrioControl(Func processAwai { try { - using (Process collapseProcess = Process.GetCurrentProcess()) - { - collapseProcess.PriorityBoostEnabled = false; - collapseProcess.PriorityClass = ProcessPriorityClass.BelowNormal; - LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Below Normal, " + - $"PriorityBoost is off, carousel is temporarily stopped", LogType.Default, true); + using Process collapseProcess = Process.GetCurrentProcess(); + collapseProcess.PriorityBoostEnabled = false; + collapseProcess.PriorityClass = ProcessPriorityClass.BelowNormal; + LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Below Normal, " + + $"PriorityBoost is off, carousel is temporarily stopped", LogType.Default, true); - double lastSlideshowDuration = ImageCarouselEventSlideshow.SlideshowDuration; - ImageCarouselEventSlideshow.SlideshowDuration = 0; // Stop slideshow duration completely - await processAwaiter(CancellationToken.None); + double lastSlideshowDuration = ImageCarouselEventSlideshow.SlideshowDuration; + ImageCarouselEventSlideshow.SlideshowDuration = 0; // Stop slideshow duration completely + await processAwaiter(CancellationToken.None); - collapseProcess.PriorityBoostEnabled = true; - collapseProcess.PriorityClass = ProcessPriorityClass.Normal; - LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Normal, " + - $"PriorityBoost is on, carousel is started", LogType.Default, true); + collapseProcess.PriorityBoostEnabled = true; + collapseProcess.PriorityClass = ProcessPriorityClass.Normal; + LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Normal, " + + $"PriorityBoost is on, carousel is started", LogType.Default, true); - ImageCarouselEventSlideshow.SlideshowDuration = lastSlideshowDuration; - } + ImageCarouselEventSlideshow.SlideshowDuration = lastSlideshowDuration; } catch (Exception ex) { diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs index 55f48f32f8..a906feebaf 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Variable.cs @@ -3,6 +3,7 @@ using CollapseLauncher.Helper.LauncherApiLoader; using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.WindowSize; using Hi3Helper.Plugin.Core.Management; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Xaml; @@ -24,8 +25,6 @@ public sealed partial class HomePage private ILauncherApi? CurrentGameLauncherApi { get; } - private HypLauncherBackgroundList? GameBackgroundData => CurrentGameLauncherApi?.LauncherGameBackground?.Data; - private HypLauncherContentKind? GameContentData => CurrentGameLauncherApi?.LauncherGameContent?.Data?.Content; internal List? GameSocialMediaData => GameContentData?.SocialMedia; @@ -52,31 +51,23 @@ public sealed partial class HomePage internal string? GamePreRegisterLink => GameInfoDisplayField?.ReservationLink?.ClickLink; - internal Visibility IsPostEventPanelVisible => GameNewsDataEventKind?.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - internal Visibility IsPostEventPanelEmpty => GameNewsDataEventKind?.Count != 0 ? Visibility.Collapsed : Visibility.Visible; - internal Visibility IsPostNoticePanelVisible => GameNewsDataAnnouncementKind?.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - internal Visibility IsPostNoticePanelEmpty => GameNewsDataAnnouncementKind?.Count != 0 ? Visibility.Collapsed : Visibility.Visible; - internal Visibility IsPostInfoPanelVisible => GameNewsDataInformationKind?.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - internal Visibility IsPostInfoPanelEmpty => GameNewsDataInformationKind?.Count != 0 ? Visibility.Collapsed : Visibility.Visible; - - internal Visibility IsPostInfoPanelAllEmpty => - IsPostEventPanelVisible == Visibility.Collapsed - && IsPostNoticePanelVisible == Visibility.Collapsed - && IsPostInfoPanelVisible == Visibility.Collapsed ? Visibility.Collapsed : Visibility.Visible; + internal bool IsNewsEventPanelVisible => GameNewsDataEventKind?.Count > 0; + internal bool IsNewsNoticePanelVisible => GameNewsDataAnnouncementKind?.Count > 0; + internal bool IsNewsInfoPanelVisible => GameNewsDataInformationKind?.Count > 0; - internal int PostEmptyMascotTextWidth => Locale.Current.Lang?._HomePage?.PostPanel_NoNews.Length > 30 ? 200 : 100; + internal int NewsEmptyMascotTextWidth => Locale.Current.Lang?._HomePage?.PostPanel_NoNews.Length > 30 ? 200 : 100; - internal int DefaultPostPanelIndex + internal int DefaultNewsPanelIndex { get { - if (IsPostEventPanelVisible != Visibility.Collapsed) + if (IsNewsEventPanelVisible) return 0; - if (IsPostNoticePanelVisible != Visibility.Collapsed) + if (IsNewsNoticePanelVisible) return 1; - return IsPostInfoPanelVisible != Visibility.Collapsed ? 2 : 0; + return IsNewsInfoPanelVisible ? 2 : 0; } } @@ -115,13 +106,13 @@ internal bool IsPlaytimeBtnVisible internal bool IsShowSidePanel { get => GetAppConfigValue("ShowEventsPanel") && - IsCarouselPanelAvailable && - IsNewsPanelAvailable; + (IsCarouselPanelAvailable || + IsNewsPanelAvailable); set { SetAndSaveConfigValue("ShowEventsPanel", value); HideImageCarousel(!value); - InnerLauncherConfig.m_mainPage?.NeedShowEventIcon = value; + HideImageEventImg(!value); } } @@ -170,6 +161,28 @@ internal string NoNewsSplashMascot } } + internal WindowSizeProp CurrentWindowSize => WindowSize.WindowSize.CurrentWindowSize; + + internal double CurrentCarouselImageWidth + { + get; + private set + { + field = value; + OnPropertyChanged(); + } + } + + internal double CurrentCarouselImageHeight + { + get; + private set + { + field = value; + OnPropertyChanged(); + } + } + internal int CurrentBannerIconHeight => CurrentGameProperty?.GamePreset.LauncherType == LauncherType.Sophon ? WindowSize.WindowSize.CurrentWindowSize.BannerIconHeight : diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 2b6d97ac88..ddfe8d3d62 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -170,6 +170,49 @@ Background="Transparent" ContextFlyout="{StaticResource BackgroundContextMenuFlyout}" DataContext="{x:Bind CurrentBackgroundManager, Mode=OneWay}" /> + + + + + + + + + + - - - - + + PointerExited="CarouselPointerExited" + Background="{ThemeResource CarouselBackgroundBrush}" + CornerRadius="8"> - + + + + CornerRadius="8" + Shadow="{ThemeResource SharedShadow}" + Translation="0,0,8"> + Text=" / " /> @@ -1328,21 +1383,20 @@ ItemsCount="{Binding ElementName=ImageCarouselEventSlideshow, Path=Items.Count, Mode=OneWay}" NextButtonStyle="{ThemeResource NewPipsPagerNextPageButtonMiniStyle}" NextNavigationButtonMode="Hidden" - NormalPipButtonStyle="{ThemeResource NewPipsPagerButtonBaseMiniStyle}" PreviousButtonStyle="{ThemeResource NewPipsPagerPreviousPageButtonMiniStyle}" PreviousNavigationButtonMode="Hidden" + NormalPipButtonStyle="{ThemeResource NewPipsPagerButtonBaseMiniStyle}" SelectedPipButtonStyle="{ThemeResource NewPipsPagerButtonBaseSelectedMiniStyle}" /> + Height="{x:Bind CurrentWindowSize.PostPanelBounds.Height, Mode=OneWay}" + Visibility="{x:Bind IsNewsPanelAvailable, Mode=OneWay, Converter={StaticResource BooleanVisibilityConverter}}"> + SelectedIndex="{x:Bind DefaultNewsPanelIndex, Mode=OneWay}">