From c5121f708ec1057a4f0a6a6e12ab503e5e17639f Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 25 Nov 2024 16:43:19 +0800 Subject: [PATCH 1/6] Restrict Game Path Access --- .../Core/Threading/AsyncReaderWriterLock.cs | 32 ++ .../Picker/IFileSystemPickerInteraction.cs | 7 +- .../Service/Abstraction/DbStoreOptions.cs | 11 +- .../Snap.Hutao/Service/Game/GameFileSystem.cs | 18 +- .../Service/Game/GameFileSystemExtension.cs | 10 +- .../Service/Game/IGameFileSystem.cs | 30 ++ .../Service/Game/IRestrictedGamePathAccess.cs | 16 + .../Snap.Hutao/Service/Game/LaunchOptions.cs | 393 +++++++++--------- .../Service/Game/LaunchOptionsExtension.cs | 66 --- .../Package/Advanced/GameInstallOptions.cs | 10 +- .../Advanced/GamePackageOperationContext.cs | 4 +- .../PackageOperationGameFileSystem.cs | 66 +++ .../Game/PathAbstraction/GamePathEntry.cs | 10 +- .../Game/RestrictedGamePathAccessExtension.cs | 89 ++++ .../InfoBarOptionsBuilderExtension.cs | 9 +- .../Notification/InfoBarServiceExtension.cs | 29 +- .../LaunchGameInstallGameDialog.xaml.cs | 13 +- .../Game/GamePackageInstallViewModel.cs | 6 +- .../ViewModel/Game/LaunchGameViewModel.cs | 9 +- .../Snap.Hutao/ViewModel/TestViewModel.cs | 2 +- 20 files changed, 514 insertions(+), 316 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/IRestrictedGamePathAccess.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncReaderWriterLock.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncReaderWriterLock.cs index 3683969eb2..1302606ea8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncReaderWriterLock.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/AsyncReaderWriterLock.cs @@ -39,6 +39,22 @@ public Task ReaderLockAsync() } } + public bool TryReaderLock(out Releaser releaser) + { + lock (waitingWriters) + { + if (status >= 0 && waitingWriters.Count == 0) + { + ++status; + releaser = new(this, false); + return true; + } + } + + releaser = default; + return false; + } + public Task WriterLockAsync() { lock (waitingWriters) @@ -55,6 +71,22 @@ public Task WriterLockAsync() } } + public bool TryWriterLock(out Releaser releaser) + { + lock (waitingWriters) + { + if (status == 0) + { + status = -1; + releaser = new(this, true); + return true; + } + } + + releaser = default; + return false; + } + private void ReaderRelease() { TaskCompletionSource? toWake = default; diff --git a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs index 713d1dc32f..c2827cd440 100644 --- a/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs +++ b/src/Snap.Hutao/Snap.Hutao/Factory/Picker/IFileSystemPickerInteraction.cs @@ -1,15 +1,16 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using JetBrains.Annotations; using Snap.Hutao.Core.IO; namespace Snap.Hutao.Factory.Picker; internal interface IFileSystemPickerInteraction { - ValueResult PickFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters); + ValueResult PickFile([LocalizationRequired] string? title, string? defaultFileName, (string Name, string Type)[]? filters); - ValueResult PickFolder(string? title); + ValueResult PickFolder([LocalizationRequired] string? title); - ValueResult SaveFile(string? title, string? defaultFileName, (string Name, string Type)[]? filters); + ValueResult SaveFile([LocalizationRequired] string? title, string? defaultFileName, (string Name, string Type)[]? filters); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs index 7f5de6358f..8ded61f6b0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/DbStoreOptions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; @@ -153,25 +154,25 @@ protected T GetOption(ref T? storage, string key, Func deserialize protected bool SetOption(ref string? storage, string key, string? value, [CallerMemberName] string? propertyName = null) { - return SetOption(ref storage, key, value, v => v, propertyName); + return SetOption(ref storage, key, value, static v => v, propertyName); } protected bool SetOption(ref bool? storage, string key, bool value, [CallerMemberName] string? propertyName = null) { - return SetOption(ref storage, key, value, v => $"{v}", propertyName); + return SetOption(ref storage, key, value, static v => $"{v}", propertyName); } protected bool SetOption(ref int? storage, string key, int value, [CallerMemberName] string? propertyName = null) { - return SetOption(ref storage, key, value, v => $"{v}", propertyName); + return SetOption(ref storage, key, value, static v => $"{v}", propertyName); } protected bool SetOption(ref float? storage, string key, float value, [CallerMemberName] string? propertyName = null) { - return SetOption(ref storage, key, value, v => $"{v}", propertyName); + return SetOption(ref storage, key, value, static v => $"{v}", propertyName); } - protected bool SetOption(ref T? storage, string key, T value, Func serializer, [CallerMemberName] string? propertyName = null) + protected bool SetOption(ref T? storage, string key, T value, [RequireStaticDelegate] Func serializer, [CallerMemberName] string? propertyName = null) { if (!SetProperty(ref storage, value, propertyName)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs index 8991f9a6bd..72bc21d087 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs @@ -6,19 +6,20 @@ namespace Snap.Hutao.Service.Game; -/// -/// A thin wrapper around the game's file path. -/// -internal sealed class GameFileSystem +internal sealed partial class GameFileSystem : IGameFileSystem { - public GameFileSystem(string gameFilePath) + private readonly AsyncReaderWriterLock.Releaser releaser; + + public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releaser) { GameFilePath = gameFilePath; + this.releaser = releaser; } - public GameFileSystem(string gameFilePath, GameAudioSystem gameAudioSystem) + public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releaser, GameAudioSystem gameAudioSystem) { GameFilePath = gameFilePath; + this.releaser = releaser; Audio = gameAudioSystem; } @@ -61,4 +62,9 @@ public string GameDirectory [field: MaybeNull] public GameAudioSystem Audio { get => field ??= new(GameFilePath); } + + public void Dispose() + { + releaser.Dispose(); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs index 9b71d58b5b..121f56afe5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game; internal static class GameFileSystemExtension { - public static bool TryGetGameVersion(this GameFileSystem gameFileSystem, [NotNullWhen(true)] out string? version) + public static bool TryGetGameVersion(this IGameFileSystem gameFileSystem, [NotNullWhen(true)] out string? version) { version = default!; if (File.Exists(gameFileSystem.GameConfigFilePath)) @@ -36,7 +36,7 @@ public static bool TryGetGameVersion(this GameFileSystem gameFileSystem, [NotNul return false; } - public static void GenerateConfigurationFile(this GameFileSystem gameFileSystem, string version, LaunchScheme launchScheme) + public static void GenerateConfigurationFile(this IGameFileSystem gameFileSystem, string version, LaunchScheme launchScheme) { string gameBiz = launchScheme.IsOversea ? "hk4e_global" : "hk4e_cn"; string content = $$$""" @@ -56,7 +56,7 @@ public static void GenerateConfigurationFile(this GameFileSystem gameFileSystem, File.WriteAllText(gameFileSystem.GameConfigFilePath, content); } - public static void UpdateConfigurationFile(this GameFileSystem gameFileSystem, string version) + public static void UpdateConfigurationFile(this IGameFileSystem gameFileSystem, string version) { List ini = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath); IniParameter gameVersion = (IniParameter)ini.Single(e => e is IniParameter { Key: "game_version" }); @@ -64,7 +64,7 @@ public static void UpdateConfigurationFile(this GameFileSystem gameFileSystem, s IniSerializer.SerializeToFile(gameFileSystem.GameConfigFilePath, ini); } - public static bool TryFixConfigurationFile(this GameFileSystem gameFileSystem, LaunchScheme launchScheme) + public static bool TryFixConfigurationFile(this IGameFileSystem gameFileSystem, LaunchScheme launchScheme) { if (!File.Exists(gameFileSystem.ScriptVersionFilePath)) { @@ -77,7 +77,7 @@ public static bool TryFixConfigurationFile(this GameFileSystem gameFileSystem, L return true; } - public static bool TryFixScriptVersion(this GameFileSystem gameFileSystem) + public static bool TryFixScriptVersion(this IGameFileSystem gameFileSystem) { if (!File.Exists(gameFileSystem.GameConfigFilePath)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs new file mode 100644 index 0000000000..6405be2b07 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs @@ -0,0 +1,30 @@ +// // Copyright (c) DGP Studio. All rights reserved. +// // Licensed under the MIT license. + +namespace Snap.Hutao.Service.Game; + +internal interface IGameFileSystem : IDisposable +{ + string GameFilePath { get; } + + string GameFileName { get; } + + string GameDirectory { get; } + + string GameConfigFilePath { get; } + + // ReSharper disable once InconsistentNaming + string PCGameSDKFilePath { get; } + + string ScreenShotDirectory { get; } + + string DataDirectory { get; } + + string ScriptVersionFilePath { get; } + + string ChunksDirectory { get; } + + string PredownloadStatusPath { get; } + + GameAudioSystem Audio { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IRestrictedGamePathAccess.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IRestrictedGamePathAccess.cs new file mode 100644 index 0000000000..e971205fcf --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IRestrictedGamePathAccess.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Game.PathAbstraction; +using System.Collections.Immutable; + +namespace Snap.Hutao.Service.Game; + +internal interface IRestrictedGamePathAccess +{ + string GamePath { get; set; } + + ImmutableArray GamePathEntries { get; set; } + + AsyncReaderWriterLock GamePathLock { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs index ae3bd9b150..5dca577aa5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.WinUI.Controls; +using JetBrains.Annotations; using Microsoft.UI.Windowing; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; @@ -13,94 +14,56 @@ using Snap.Hutao.Win32.Graphics.Gdi; using System.Collections.Immutable; using System.Globalization; -using Windows.Graphics; +using System.Runtime.InteropServices; using static Snap.Hutao.Win32.Gdi32; using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.Service.Game; [Injection(InjectAs.Singleton)] -internal sealed partial class LaunchOptions : DbStoreOptions, IRecipient +internal sealed partial class LaunchOptions : DbStoreOptions, + IRestrictedGamePathAccess, + IRecipient { private readonly ITaskContext taskContext; - - private readonly int primaryScreenWidth; - private readonly int primaryScreenHeight; - private readonly int primaryScreenFps; - - private ImmutableArray? gamePathEntries; - - private bool? usingHoyolabAccount; - private bool? areCommandLineArgumentsEnabled; - private bool? isFullScreen; - private bool? isBorderless; - private bool? isExclusive; - private int? screenWidth; - private bool? isScreenWidthEnabled; - private int? screenHeight; - private bool? isScreenHeightEnabled; - - private bool? isIslandEnabled; - private bool? hookingSetFieldOfView; - private bool? isSetFieldOfViewEnabled; - private float? targetFov; - private bool? fixLowFovScene; - private bool? disableFog; - private bool? isSetTargetFrameRateEnabled; - private int? targetFps; - private bool? hookingOpenTeam; - private bool? removeOpenTeamProgress; - private bool? hookingMickyWonderPartner2; - private bool? isMonitorEnabled; - private bool? usingCloudThirdPartyMobile; - private bool? isWindowsHDREnabled; - private bool? usingStarwardPlayTimeStatistics; - private bool? usingBetterGenshinImpactAutomation; - private bool? setDiscordActivityWhenPlaying; + private Fields fields; public LaunchOptions(IServiceProvider serviceProvider) : base(serviceProvider) { taskContext = serviceProvider.GetRequiredService(); - RectInt32 primaryRect = DisplayArea.Primary.OuterBounds; - primaryScreenWidth = primaryRect.Width; - primaryScreenHeight = primaryRect.Height; - - Monitors = InitializeMonitors(); - InitializeScreenFps(out primaryScreenFps); - // Batch initialization, boost up performance InitializeOptions(entry => entry.Key.StartsWith("Launch."), (key, value) => { _ = key switch { - SettingEntry.LaunchUsingHoyolabAccount => InitializeBooleanValue(ref usingHoyolabAccount, value), - SettingEntry.LaunchAreCommandLineArgumentsEnabled => InitializeBooleanValue(ref areCommandLineArgumentsEnabled, value), - SettingEntry.LaunchIsFullScreen => InitializeBooleanValue(ref isFullScreen, value), - SettingEntry.LaunchIsBorderless => InitializeBooleanValue(ref isBorderless, value), - SettingEntry.LaunchIsExclusive => InitializeBooleanValue(ref isExclusive, value), - SettingEntry.LaunchScreenWidth => InitializeInt32Value(ref screenWidth, value), - SettingEntry.LaunchIsScreenWidthEnabled => InitializeBooleanValue(ref isScreenWidthEnabled, value), - SettingEntry.LaunchScreenHeight => InitializeInt32Value(ref screenHeight, value), - SettingEntry.LaunchIsScreenHeightEnabled => InitializeBooleanValue(ref isScreenHeightEnabled, value), - SettingEntry.LaunchIsMonitorEnabled => InitializeBooleanValue(ref isMonitorEnabled, value), - SettingEntry.LaunchUsingCloudThirdPartyMobile => InitializeBooleanValue(ref usingCloudThirdPartyMobile, value), - SettingEntry.LaunchIsWindowsHDREnabled => InitializeBooleanValue(ref isWindowsHDREnabled, value), - SettingEntry.LaunchUsingStarwardPlayTimeStatistics => InitializeBooleanValue(ref usingStarwardPlayTimeStatistics, value), - SettingEntry.LaunchUsingBetterGenshinImpactAutomation => InitializeBooleanValue(ref usingBetterGenshinImpactAutomation, value), - SettingEntry.LaunchSetDiscordActivityWhenPlaying => InitializeBooleanValue(ref setDiscordActivityWhenPlaying, value), - SettingEntry.LaunchIsIslandEnabled => InitializeBooleanValue(ref isIslandEnabled, value), - SettingEntry.LaunchHookingSetFieldOfView => InitializeBooleanValue(ref hookingSetFieldOfView, value), - SettingEntry.LaunchIsSetFieldOfViewEnabled => InitializeBooleanValue(ref isSetFieldOfViewEnabled, value), - SettingEntry.LaunchTargetFov => InitializeFloatValue(ref targetFov, value), - SettingEntry.LaunchFixLowFovScene => InitializeBooleanValue(ref fixLowFovScene, value), - SettingEntry.LaunchDisableFog => InitializeBooleanValue(ref disableFog, value), - SettingEntry.LaunchIsSetTargetFrameRateEnabled => InitializeBooleanValue(ref isSetTargetFrameRateEnabled, value), - SettingEntry.LaunchTargetFps => InitializeInt32Value(ref targetFps, value), - SettingEntry.LaunchHookingOpenTeam => InitializeBooleanValue(ref hookingOpenTeam, value), - SettingEntry.LaunchRemoveOpenTeamProgress => InitializeBooleanValue(ref removeOpenTeamProgress, value), - SettingEntry.LaunchHookingMickyWonderPartner2 => InitializeBooleanValue(ref hookingMickyWonderPartner2, value), + SettingEntry.LaunchUsingHoyolabAccount => InitializeNullableBooleanValue(ref fields.UsingHoyolabAccount, value), + SettingEntry.LaunchAreCommandLineArgumentsEnabled => InitializeNullableBooleanValue(ref fields.AreCommandLineArgumentsEnabled, value), + SettingEntry.LaunchIsFullScreen => InitializeNullableBooleanValue(ref fields.IsFullScreen, value), + SettingEntry.LaunchIsBorderless => InitializeNullableBooleanValue(ref fields.IsBorderless, value), + SettingEntry.LaunchIsExclusive => InitializeNullableBooleanValue(ref fields.IsExclusive, value), + SettingEntry.LaunchScreenWidth => InitializeNullableInt32Value(ref fields.ScreenWidth, value), + SettingEntry.LaunchIsScreenWidthEnabled => InitializeNullableBooleanValue(ref fields.IsScreenWidthEnabled, value), + SettingEntry.LaunchScreenHeight => InitializeNullableInt32Value(ref fields.ScreenHeight, value), + SettingEntry.LaunchIsScreenHeightEnabled => InitializeNullableBooleanValue(ref fields.IsScreenHeightEnabled, value), + SettingEntry.LaunchIsMonitorEnabled => InitializeNullableBooleanValue(ref fields.IsMonitorEnabled, value), + SettingEntry.LaunchUsingCloudThirdPartyMobile => InitializeNullableBooleanValue(ref fields.UsingCloudThirdPartyMobile, value), + SettingEntry.LaunchIsWindowsHDREnabled => InitializeNullableBooleanValue(ref fields.IsWindowsHDREnabled, value), + SettingEntry.LaunchUsingStarwardPlayTimeStatistics => InitializeNullableBooleanValue(ref fields.UsingStarwardPlayTimeStatistics, value), + SettingEntry.LaunchUsingBetterGenshinImpactAutomation => InitializeNullableBooleanValue(ref fields.UsingBetterGenshinImpactAutomation, value), + SettingEntry.LaunchSetDiscordActivityWhenPlaying => InitializeNullableBooleanValue(ref fields.SetDiscordActivityWhenPlaying, value), + SettingEntry.LaunchIsIslandEnabled => InitializeNullableBooleanValue(ref fields.IsIslandEnabled, value), + SettingEntry.LaunchHookingSetFieldOfView => InitializeNullableBooleanValue(ref fields.HookingSetFieldOfView, value), + SettingEntry.LaunchIsSetFieldOfViewEnabled => InitializeNullableBooleanValue(ref fields.IsSetFieldOfViewEnabled, value), + SettingEntry.LaunchTargetFov => InitializeNullableFloatValue(ref fields.TargetFov, value), + SettingEntry.LaunchFixLowFovScene => InitializeNullableBooleanValue(ref fields.FixLowFovScene, value), + SettingEntry.LaunchDisableFog => InitializeNullableBooleanValue(ref fields.DisableFog, value), + SettingEntry.LaunchIsSetTargetFrameRateEnabled => InitializeNullableBooleanValue(ref fields.IsSetTargetFrameRateEnabled, value), + SettingEntry.LaunchTargetFps => InitializeNullableInt32Value(ref fields.TargetFps, value), + SettingEntry.LaunchHookingOpenTeam => InitializeNullableBooleanValue(ref fields.HookingOpenTeam, value), + SettingEntry.LaunchRemoveOpenTeamProgress => InitializeNullableBooleanValue(ref fields.RemoveOpenTeamProgress, value), + SettingEntry.LaunchHookingMickyWonderPartner2 => InitializeNullableBooleanValue(ref fields.HookingMickyWonderPartner2, value), _ => default, }; }); @@ -108,74 +71,43 @@ public LaunchOptions(IServiceProvider serviceProvider) IslandFeatureStateMachine = new(this); serviceProvider.GetRequiredService().Register(this); - static Void InitializeBooleanValue(ref bool? storage, string? value) + static Void InitializeNullableBooleanValue(ref bool? storage, string? value) { - if (value is not null) + if (value is null) { - bool.TryParse(value, out bool result); - storage = result; + return default; } - return default; - } - - static Void InitializeInt32Value(ref int? storage, string? value) - { - if (value is not null) - { - int.TryParse(value, CultureInfo.InvariantCulture, out int result); - storage = result; - } + bool.TryParse(value, out bool result); + storage = result; return default; } - static Void InitializeFloatValue(ref float? storage, string? value) + static Void InitializeNullableInt32Value(ref int? storage, string? value) { - if (value is not null) + if (value is null) { - float.TryParse(value, CultureInfo.InvariantCulture, out float result); - storage = result; + return default; } + int.TryParse(value, CultureInfo.InvariantCulture, out int result); + storage = result; + return default; } - static ImmutableArray> InitializeMonitors() + static Void InitializeNullableFloatValue(ref float? storage, string? value) { - ImmutableArray>.Builder monitors = ImmutableArray.CreateBuilder>(); - try - { - // This list can't use foreach - // https://github.com/microsoft/CsWinRT/issues/747 - IReadOnlyList displayAreas = DisplayArea.FindAll(); - for (int i = 0; i < displayAreas.Count; i++) - { - DisplayArea displayArea = displayAreas[i]; - int index = i + 1; - monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index)); - } - } - catch + if (value is null) { - monitors.Clear(); + return default; } - return monitors.ToImmutable(); - } + float.TryParse(value, CultureInfo.InvariantCulture, out float result); + storage = result; - static void InitializeScreenFps(out int fps) - { - HDC hDC = default; - try - { - hDC = GetDC(default); - fps = GetDeviceCaps(hDC, GET_DEVICE_CAPS_INDEX.VREFRESH); - } - finally - { - _ = ReleaseDC(default, hDC); - } + return default; } } @@ -190,26 +122,25 @@ public ImmutableArray GamePathEntries { // Because DbStoreOptions can't detect collection change, We use // ImmutableList to imply that the whole list needs to be replaced - get => GetOption(ref gamePathEntries, SettingEntry.GamePathEntries, raw => JsonSerializer.Deserialize>(raw), []).Value; - set => SetOption(ref gamePathEntries, SettingEntry.GamePathEntries, value, v => JsonSerializer.Serialize(v)); + get => GetOption(ref fields.GamePathEntries, SettingEntry.GamePathEntries, raw => JsonSerializer.Deserialize>(raw), []).Value; + set => SetOption(ref fields.GamePathEntries, SettingEntry.GamePathEntries, value, static v => JsonSerializer.Serialize(v)); } - #region Launch Prefixed Options - - #region CLI Options + public AsyncReaderWriterLock GamePathLock { get; } = new(); public bool UsingHoyolabAccount { - get => GetOption(ref usingHoyolabAccount, SettingEntry.LaunchUsingHoyolabAccount, false); - set => SetOption(ref usingHoyolabAccount, SettingEntry.LaunchUsingHoyolabAccount, value); + get => GetOption(ref fields.UsingHoyolabAccount, SettingEntry.LaunchUsingHoyolabAccount, false); + set => SetOption(ref fields.UsingHoyolabAccount, SettingEntry.LaunchUsingHoyolabAccount, value); } + [UsedImplicitly] public bool AreCommandLineArgumentsEnabled { - get => GetOption(ref areCommandLineArgumentsEnabled, SettingEntry.LaunchAreCommandLineArgumentsEnabled, true); + get => GetOption(ref fields.AreCommandLineArgumentsEnabled, SettingEntry.LaunchAreCommandLineArgumentsEnabled, true); set { - if (SetOption(ref areCommandLineArgumentsEnabled, SettingEntry.LaunchAreCommandLineArgumentsEnabled, value)) + if (SetOption(ref fields.AreCommandLineArgumentsEnabled, SettingEntry.LaunchAreCommandLineArgumentsEnabled, value)) { if (!value) { @@ -219,51 +150,57 @@ public bool AreCommandLineArgumentsEnabled } } + [UsedImplicitly] public bool IsFullScreen { - get => GetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen, false); - set => SetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen, value); + get => GetOption(ref fields.IsFullScreen, SettingEntry.LaunchIsFullScreen, false); + set => SetOption(ref fields.IsFullScreen, SettingEntry.LaunchIsFullScreen, value); } + [UsedImplicitly] public bool IsBorderless { - get => GetOption(ref isBorderless, SettingEntry.LaunchIsBorderless); - set => SetOption(ref isBorderless, SettingEntry.LaunchIsBorderless, value); + get => GetOption(ref fields.IsBorderless, SettingEntry.LaunchIsBorderless); + set => SetOption(ref fields.IsBorderless, SettingEntry.LaunchIsBorderless, value); } + [UsedImplicitly] public bool IsExclusive { - get => GetOption(ref isExclusive, SettingEntry.LaunchIsExclusive); - set => SetOption(ref isExclusive, SettingEntry.LaunchIsExclusive, value); + get => GetOption(ref fields.IsExclusive, SettingEntry.LaunchIsExclusive); + set => SetOption(ref fields.IsExclusive, SettingEntry.LaunchIsExclusive, value); } public int ScreenWidth { - get => GetOption(ref screenWidth, SettingEntry.LaunchScreenWidth, primaryScreenWidth); - set => SetOption(ref screenWidth, SettingEntry.LaunchScreenWidth, value); + get => GetOption(ref fields.ScreenWidth, SettingEntry.LaunchScreenWidth, DisplayArea.Primary.OuterBounds.Width); + set => SetOption(ref fields.ScreenWidth, SettingEntry.LaunchScreenWidth, value); } + [UsedImplicitly] public bool IsScreenWidthEnabled { - get => GetOption(ref isScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, true); - set => SetOption(ref isScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, value); + get => GetOption(ref fields.IsScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, true); + set => SetOption(ref fields.IsScreenWidthEnabled, SettingEntry.LaunchIsScreenWidthEnabled, value); } public int ScreenHeight { - get => GetOption(ref screenHeight, SettingEntry.LaunchScreenHeight, primaryScreenHeight); - set => SetOption(ref screenHeight, SettingEntry.LaunchScreenHeight, value); + get => GetOption(ref fields.ScreenHeight, SettingEntry.LaunchScreenHeight, DisplayArea.Primary.OuterBounds.Height); + set => SetOption(ref fields.ScreenHeight, SettingEntry.LaunchScreenHeight, value); } + [UsedImplicitly] public bool IsScreenHeightEnabled { - get => GetOption(ref isScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, true); - set => SetOption(ref isScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, value); + get => GetOption(ref fields.IsScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, true); + set => SetOption(ref fields.IsScreenHeightEnabled, SettingEntry.LaunchIsScreenHeightEnabled, value); } - public ImmutableArray> Monitors { get; } + public ImmutableArray> Monitors { get; } = InitializeMonitors(); - [NotNull] + [UsedImplicitly] + [System.Diagnostics.CodeAnalysis.NotNull] public NameValue? Monitor { get @@ -280,154 +217,164 @@ static int RestrictIndex(ImmutableArray> monitors, string index) { if (value is not null) { - SetOption(ref field, SettingEntry.LaunchMonitor, value, selected => selected.Value.ToString(CultureInfo.InvariantCulture)); + SetOption(ref field, SettingEntry.LaunchMonitor, value, static selected => selected.Value.ToString(CultureInfo.InvariantCulture)); } } } + [UsedImplicitly] public bool IsMonitorEnabled { - get => GetOption(ref isMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, true); - set => SetOption(ref isMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, value); + get => GetOption(ref fields.IsMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, true); + set => SetOption(ref fields.IsMonitorEnabled, SettingEntry.LaunchIsMonitorEnabled, value); } + [UsedImplicitly] public bool UsingCloudThirdPartyMobile { - get => GetOption(ref usingCloudThirdPartyMobile, SettingEntry.LaunchUsingCloudThirdPartyMobile, false); - set => SetOption(ref usingCloudThirdPartyMobile, SettingEntry.LaunchUsingCloudThirdPartyMobile, value); + get => GetOption(ref fields.UsingCloudThirdPartyMobile, SettingEntry.LaunchUsingCloudThirdPartyMobile, false); + set => SetOption(ref fields.UsingCloudThirdPartyMobile, SettingEntry.LaunchUsingCloudThirdPartyMobile, value); } + // ReSharper disable once InconsistentNaming + [UsedImplicitly] public bool IsWindowsHDREnabled { - get => GetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, false); - set => SetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, value); + get => GetOption(ref fields.IsWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, false); + set => SetOption(ref fields.IsWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, value); } - #endregion - - #region InterProcess + [UsedImplicitly] public bool UsingStarwardPlayTimeStatistics { - get => GetOption(ref usingStarwardPlayTimeStatistics, SettingEntry.LaunchUsingStarwardPlayTimeStatistics, false); - set => SetOption(ref usingStarwardPlayTimeStatistics, SettingEntry.LaunchUsingStarwardPlayTimeStatistics, value); + get => GetOption(ref fields.UsingStarwardPlayTimeStatistics, SettingEntry.LaunchUsingStarwardPlayTimeStatistics, false); + set => SetOption(ref fields.UsingStarwardPlayTimeStatistics, SettingEntry.LaunchUsingStarwardPlayTimeStatistics, value); } + [UsedImplicitly] public bool UsingBetterGenshinImpactAutomation { - get => GetOption(ref usingBetterGenshinImpactAutomation, SettingEntry.LaunchUsingBetterGenshinImpactAutomation, false); - set => SetOption(ref usingBetterGenshinImpactAutomation, SettingEntry.LaunchUsingBetterGenshinImpactAutomation, value); + get => GetOption(ref fields.UsingBetterGenshinImpactAutomation, SettingEntry.LaunchUsingBetterGenshinImpactAutomation, false); + set => SetOption(ref fields.UsingBetterGenshinImpactAutomation, SettingEntry.LaunchUsingBetterGenshinImpactAutomation, value); } + [UsedImplicitly] public bool SetDiscordActivityWhenPlaying { - get => GetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true); - set => SetOption(ref setDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, value); + get => GetOption(ref fields.SetDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, true); + set => SetOption(ref fields.SetDiscordActivityWhenPlaying, SettingEntry.LaunchSetDiscordActivityWhenPlaying, value); } - #endregion - - #region Island Features public LaunchOptionsIslandFeatureStateMachine IslandFeatureStateMachine { get; } + [UsedImplicitly] public bool IsIslandEnabled { - get => GetOption(ref isIslandEnabled, SettingEntry.LaunchIsIslandEnabled, false); + get => GetOption(ref fields.IsIslandEnabled, SettingEntry.LaunchIsIslandEnabled, false); set { - if (SetOption(ref isIslandEnabled, SettingEntry.LaunchIsIslandEnabled, value)) + if (SetOption(ref fields.IsIslandEnabled, SettingEntry.LaunchIsIslandEnabled, value)) { IslandFeatureStateMachine.Update(this); } } } + [UsedImplicitly] public bool HookingSetFieldOfView { - get => GetOption(ref hookingSetFieldOfView, SettingEntry.LaunchHookingSetFieldOfView, true); + get => GetOption(ref fields.HookingSetFieldOfView, SettingEntry.LaunchHookingSetFieldOfView, true); set { - if (SetOption(ref hookingSetFieldOfView, SettingEntry.LaunchHookingSetFieldOfView, value)) + if (SetOption(ref fields.HookingSetFieldOfView, SettingEntry.LaunchHookingSetFieldOfView, value)) { IslandFeatureStateMachine.Update(this); } } } + [UsedImplicitly] public bool IsSetFieldOfViewEnabled { - get => GetOption(ref isSetFieldOfViewEnabled, SettingEntry.LaunchIsSetFieldOfViewEnabled, true); + get => GetOption(ref fields.IsSetFieldOfViewEnabled, SettingEntry.LaunchIsSetFieldOfViewEnabled, true); set { - if (SetOption(ref isSetFieldOfViewEnabled, SettingEntry.LaunchIsSetFieldOfViewEnabled, value)) + if (SetOption(ref fields.IsSetFieldOfViewEnabled, SettingEntry.LaunchIsSetFieldOfViewEnabled, value)) { IslandFeatureStateMachine.Update(this); } } } + [UsedImplicitly] public float TargetFov { - get => GetOption(ref targetFov, SettingEntry.LaunchTargetFov, 45f); - set => SetOption(ref targetFov, SettingEntry.LaunchTargetFov, value); + get => GetOption(ref fields.TargetFov, SettingEntry.LaunchTargetFov, 45f); + set => SetOption(ref fields.TargetFov, SettingEntry.LaunchTargetFov, value); } + [UsedImplicitly] public bool FixLowFovScene { - get => GetOption(ref fixLowFovScene, SettingEntry.LaunchFixLowFovScene, true); - set => SetOption(ref fixLowFovScene, SettingEntry.LaunchFixLowFovScene, value); + get => GetOption(ref fields.FixLowFovScene, SettingEntry.LaunchFixLowFovScene, true); + set => SetOption(ref fields.FixLowFovScene, SettingEntry.LaunchFixLowFovScene, value); } + [UsedImplicitly] public bool DisableFog { - get => GetOption(ref disableFog, SettingEntry.LaunchDisableFog, false); - set => SetOption(ref disableFog, SettingEntry.LaunchDisableFog, value); + get => GetOption(ref fields.DisableFog, SettingEntry.LaunchDisableFog, false); + set => SetOption(ref fields.DisableFog, SettingEntry.LaunchDisableFog, value); } + [UsedImplicitly] public bool IsSetTargetFrameRateEnabled { - get => GetOption(ref isSetTargetFrameRateEnabled, SettingEntry.LaunchIsSetTargetFrameRateEnabled, true); + get => GetOption(ref fields.IsSetTargetFrameRateEnabled, SettingEntry.LaunchIsSetTargetFrameRateEnabled, true); set { - if (SetOption(ref isSetTargetFrameRateEnabled, SettingEntry.LaunchIsSetTargetFrameRateEnabled, value)) + if (SetOption(ref fields.IsSetTargetFrameRateEnabled, SettingEntry.LaunchIsSetTargetFrameRateEnabled, value)) { IslandFeatureStateMachine.Update(this); } } } + [UsedImplicitly] public int TargetFps { - get => GetOption(ref targetFps, SettingEntry.LaunchTargetFps, primaryScreenFps); - set => SetOption(ref targetFps, SettingEntry.LaunchTargetFps, value); + get => GetOption(ref fields.TargetFps, SettingEntry.LaunchTargetFps, InitializeScreenFps); + set => SetOption(ref fields.TargetFps, SettingEntry.LaunchTargetFps, value); } + [UsedImplicitly] public bool HookingOpenTeam { - get => GetOption(ref hookingOpenTeam, SettingEntry.LaunchHookingOpenTeam, true); + get => GetOption(ref fields.HookingOpenTeam, SettingEntry.LaunchHookingOpenTeam, true); set { - if (SetOption(ref hookingOpenTeam, SettingEntry.LaunchHookingOpenTeam, value)) + if (SetOption(ref fields.HookingOpenTeam, SettingEntry.LaunchHookingOpenTeam, value)) { IslandFeatureStateMachine.Update(this); } } } + [UsedImplicitly] public bool RemoveOpenTeamProgress { - get => GetOption(ref removeOpenTeamProgress, SettingEntry.LaunchRemoveOpenTeamProgress, false); - set => SetOption(ref removeOpenTeamProgress, SettingEntry.LaunchRemoveOpenTeamProgress, value); + get => GetOption(ref fields.RemoveOpenTeamProgress, SettingEntry.LaunchRemoveOpenTeamProgress, false); + set => SetOption(ref fields.RemoveOpenTeamProgress, SettingEntry.LaunchRemoveOpenTeamProgress, value); } + [UsedImplicitly] public bool HookingMickyWonderPartner2 { - get => GetOption(ref hookingMickyWonderPartner2, SettingEntry.LaunchHookingMickyWonderPartner2, true); - set => SetOption(ref hookingMickyWonderPartner2, SettingEntry.LaunchHookingMickyWonderPartner2, value); + get => GetOption(ref fields.HookingMickyWonderPartner2, SettingEntry.LaunchHookingMickyWonderPartner2, true); + set => SetOption(ref fields.HookingMickyWonderPartner2, SettingEntry.LaunchHookingMickyWonderPartner2, value); } - #endregion - - #endregion + [UsedImplicitly] public ImmutableArray AspectRatios { get; } = [ new(3840, 2160), @@ -437,6 +384,7 @@ public bool HookingMickyWonderPartner2 new(1920, 1080), ]; + [UsedImplicitly] public AspectRatio? SelectedAspectRatio { get; @@ -461,4 +409,77 @@ public void Receive(LaunchExecutionProcessStatusChangedMessage message) OnPropertyChanged(nameof(IsGameRunning)); }); } + + private static int InitializeScreenFps() + { + HDC dc = default; + try + { + dc = GetDC(default); + return GetDeviceCaps(dc, GET_DEVICE_CAPS_INDEX.VREFRESH); + } + finally + { + _ = ReleaseDC(default, dc); + } + } + + private static ImmutableArray> InitializeMonitors() + { + ImmutableArray>.Builder monitors = ImmutableArray.CreateBuilder>(); + try + { + // This list can't use foreach + // https://github.com/microsoft/CsWinRT/issues/747 + IReadOnlyList displayAreas = DisplayArea.FindAll(); + for (int i = 0; i < displayAreas.Count; i++) + { + DisplayArea displayArea = displayAreas[i]; + int index = i + 1; + monitors.Add(new($"{displayArea.DisplayId.Value:X8}:{index}", index)); + } + } + catch + { + monitors.Clear(); + } + + return monitors.ToImmutable(); + } + + [StructLayout(LayoutKind.Auto)] + private struct Fields + { + public ImmutableArray? GamePathEntries; + + public bool? UsingHoyolabAccount; + public bool? AreCommandLineArgumentsEnabled; + public bool? IsFullScreen; + public bool? IsBorderless; + public bool? IsExclusive; + public int? ScreenWidth; + public bool? IsScreenWidthEnabled; + public int? ScreenHeight; + public bool? IsScreenHeightEnabled; + + public bool? IsIslandEnabled; + public bool? HookingSetFieldOfView; + public bool? IsSetFieldOfViewEnabled; + public float? TargetFov; + public bool? FixLowFovScene; + public bool? DisableFog; + public bool? IsSetTargetFrameRateEnabled; + public int? TargetFps; + public bool? HookingOpenTeam; + public bool? RemoveOpenTeamProgress; + public bool? HookingMickyWonderPartner2; + public bool? IsMonitorEnabled; + public bool? UsingCloudThirdPartyMobile; + + // ReSharper disable once InconsistentNaming + public bool? IsWindowsHDREnabled; + public bool? UsingStarwardPlayTimeStatistics; + public bool? UsingBetterGenshinImpactAutomation; + public bool? SetDiscordActivityWhenPlaying; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs deleted file mode 100644 index e43821209e..0000000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptionsExtension.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Service.Game.PathAbstraction; -using System.Collections.Immutable; - -namespace Snap.Hutao.Service.Game; - -internal static class LaunchOptionsExtension -{ - public static bool TryGetGameFileSystem(this LaunchOptions options, [NotNullWhen(true)] out GameFileSystem? fileSystem) - { - string gamePath = options.GamePath; - - if (string.IsNullOrEmpty(gamePath)) - { - fileSystem = default; - return false; - } - - fileSystem = new(gamePath); - return true; - } - - public static ImmutableArray GetGamePathEntries(this LaunchOptions options, out GamePathEntry? selected) - { - string gamePath = options.GamePath; - - if (string.IsNullOrEmpty(gamePath)) - { - selected = default; - return options.GamePathEntries; - } - - if (options.GamePathEntries.SingleOrDefault(entry => string.Equals(entry.Path, options.GamePath, StringComparison.OrdinalIgnoreCase)) is { } existed) - { - selected = existed; - return options.GamePathEntries; - } - - selected = GamePathEntry.Create(options.GamePath); - return options.GamePathEntries = options.GamePathEntries.Add(selected); - } - - public static ImmutableArray RemoveGamePathEntry(this LaunchOptions options, GamePathEntry? entry, out GamePathEntry? selected) - { - if (entry is null) - { - return options.GetGamePathEntries(out selected); - } - - if (string.Equals(options.GamePath, entry.Path, StringComparison.OrdinalIgnoreCase)) - { - options.GamePath = string.Empty; - } - - options.GamePathEntries = options.GamePathEntries.Remove(entry); - return options.GetGamePathEntries(out selected); - } - - public static ImmutableArray UpdateGamePath(this LaunchOptions options, string gamePath) - { - options.GamePath = gamePath; - return options.GetGamePathEntries(out _); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameInstallOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameInstallOptions.cs index e3295ba60e..6bf38952e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameInstallOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameInstallOptions.cs @@ -6,20 +6,20 @@ namespace Snap.Hutao.Service.Game.Package.Advanced; -internal readonly struct GameInstallOptions : IDeconstruct +internal readonly struct GameInstallOptions : IDeconstruct { - public readonly GameFileSystem GameFileSystem; + public readonly IGameFileSystem GameFileSystem; public readonly LaunchScheme LaunchScheme; - public GameInstallOptions(GameFileSystem gameFileSystem, LaunchScheme launchScheme) + public GameInstallOptions(IGameFileSystem gameFileSystem, LaunchScheme launchScheme) { GameFileSystem = gameFileSystem; LaunchScheme = launchScheme; } - public void Deconstruct(out GameFileSystem gameFileSystem, out LaunchScheme launchScheme) + public void Deconstruct(out IGameFileSystem gameFileSystem, out LaunchScheme launchScheme) { gameFileSystem = GameFileSystem; launchScheme = LaunchScheme; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs index 6ae2faa785..6d5e10838c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs @@ -12,7 +12,7 @@ internal readonly struct GamePackageOperationContext { public readonly GamePackageOperationKind Kind; public readonly IGameAssetOperation Asset; - public readonly GameFileSystem GameFileSystem; + public readonly IGameFileSystem GameFileSystem; public readonly BranchWrapper LocalBranch; public readonly BranchWrapper RemoteBranch; public readonly GameChannelSDK? GameChannelSDK; @@ -22,7 +22,7 @@ internal readonly struct GamePackageOperationContext public GamePackageOperationContext( IServiceProvider serviceProvider, GamePackageOperationKind kind, - GameFileSystem gameFileSystem, + IGameFileSystem gameFileSystem, BranchWrapper localBranch, BranchWrapper remoteBranch, GameChannelSDK? gameChannelSDK, diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs new file mode 100644 index 0000000000..3b9a60dd18 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs @@ -0,0 +1,66 @@ +// // Copyright (c) DGP Studio. All rights reserved. +// // Licensed under the MIT license. + +using Snap.Hutao.Service.Game.Scheme; +using System.IO; + +namespace Snap.Hutao.Service.Game.Package.Advanced; + +internal sealed partial class PackageOperationGameFileSystem : IGameFileSystem +{ + public PackageOperationGameFileSystem(string gameFilePath) + { + GameFilePath = gameFilePath; + } + + public PackageOperationGameFileSystem(string gameFilePath, GameAudioSystem gameAudioSystem) + { + GameFilePath = gameFilePath; + Audio = gameAudioSystem; + } + + public string GameFilePath { get; } + + [field: MaybeNull] + public string GameFileName { get => field ??= Path.GetFileName(GameFilePath); } + + [field: MaybeNull] + public string GameDirectory + { + get + { + if (field is not null) + { + return field; + } + + string? directoryName = Path.GetDirectoryName(GameFilePath); + ArgumentException.ThrowIfNullOrEmpty(directoryName); + return field = directoryName; + } + } + + [field: MaybeNull] + public string GameConfigFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); } + + // ReSharper disable once InconsistentNaming + [field: MaybeNull] + public string PCGameSDKFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } + + public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); } + + public string DataDirectory { get => Path.Combine(GameDirectory, LaunchScheme.ExecutableIsOversea(GameFileName) ? GameConstants.GenshinImpactData : GameConstants.YuanShenData); } + + public string ScriptVersionFilePath { get => Path.Combine(DataDirectory, "Persistent", "ScriptVersion"); } + + public string ChunksDirectory { get => Path.Combine(GameDirectory, "chunks"); } + + public string PredownloadStatusPath { get => Path.Combine(ChunksDirectory, "snap_hutao_predownload_status.json"); } + + [field: MaybeNull] + public GameAudioSystem Audio { get => field ??= new(GameFilePath); } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs index 3955a525b0..536b6488c6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs @@ -6,10 +6,7 @@ namespace Snap.Hutao.Service.Game.PathAbstraction; internal sealed class GamePathEntry { [JsonPropertyName("Path")] - public string Path { get; set; } = default!; - - [JsonIgnore] - public GamePathEntryKind Kind { get => GetKind(Path); } + public string Path { get; private set; } = default!; public static GamePathEntry Create(string path) { @@ -18,9 +15,4 @@ public static GamePathEntry Create(string path) Path = path, }; } - - private static GamePathEntryKind GetKind(string path) - { - return GamePathEntryKind.None; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs new file mode 100644 index 0000000000..6556ef49ff --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs @@ -0,0 +1,89 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Service.Game.PathAbstraction; +using System.Collections.Immutable; + +namespace Snap.Hutao.Service.Game; + +internal static class RestrictedGamePathAccessExtension +{ + public static bool TryGetGameFileSystem(this IRestrictedGamePathAccess access, [NotNullWhen(true)] out GameFileSystem? fileSystem) + { + string gamePath = access.GamePath; + + if (string.IsNullOrEmpty(gamePath)) + { + fileSystem = default; + return false; + } + + if (!access.GamePathLock.TryReaderLock(out AsyncReaderWriterLock.Releaser releaser)) + { + fileSystem = default; + return false; + } + + fileSystem = new(gamePath, releaser); + return true; + } + + public static ImmutableArray GetGamePathEntries(this IRestrictedGamePathAccess access, out GamePathEntry? selected) + { + string gamePath = access.GamePath; + + if (string.IsNullOrEmpty(gamePath)) + { + selected = default; + return access.GamePathEntries; + } + + if (access.GamePathEntries.SingleOrDefault(entry => string.Equals(entry.Path, access.GamePath, StringComparison.OrdinalIgnoreCase)) is { } existed) + { + selected = existed; + return access.GamePathEntries; + } + + selected = GamePathEntry.Create(access.GamePath); + return access.GamePathEntries = access.GamePathEntries.Add(selected); + } + + public static ImmutableArray RemoveGamePathEntry(this IRestrictedGamePathAccess access, GamePathEntry? entry, out GamePathEntry? selected) + { + if (entry is null) + { + return access.GetGamePathEntries(out selected); + } + + if (!access.GamePathLock.TryWriterLock(out AsyncReaderWriterLock.Releaser releaser)) + { + throw HutaoException.InvalidOperation("Cannot remove game path while it is being used."); + } + + using (releaser) + { + if (string.Equals(access.GamePath, entry.Path, StringComparison.OrdinalIgnoreCase)) + { + access.GamePath = string.Empty; + } + + access.GamePathEntries = access.GamePathEntries.Remove(entry); + return access.GetGamePathEntries(out selected); + } + } + + public static ImmutableArray UpdateGamePath(this IRestrictedGamePathAccess access, string gamePath) + { + if (!access.GamePathLock.TryWriterLock(out AsyncReaderWriterLock.Releaser releaser)) + { + throw HutaoException.InvalidOperation("Cannot update game path while it is being used."); + } + + using (releaser) + { + access.GamePath = gamePath; + return access.GetGamePathEntries(out _); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarOptionsBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarOptionsBuilderExtension.cs index 5f9b0080bc..72584891d5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarOptionsBuilderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarOptionsBuilderExtension.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using JetBrains.Annotations; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core.Abstraction; @@ -15,14 +16,14 @@ public static TBuilder SetSeverity(this TBuilder builder, InfoBarSever return builder; } - public static TBuilder SetTitle(this TBuilder builder, string? title) + public static TBuilder SetTitle(this TBuilder builder, [LocalizationRequired] string? title) where TBuilder : IInfoBarOptionsBuilder { builder.Configure(builder => builder.Options.Title = title); return builder; } - public static IInfoBarOptionsBuilder SetMessage(this TBuilder builder, string? message) + public static IInfoBarOptionsBuilder SetMessage(this TBuilder builder, [LocalizationRequired] string? message) where TBuilder : IInfoBarOptionsBuilder { builder.Configure(builder => builder.Options.Message = message); @@ -36,7 +37,7 @@ public static IInfoBarOptionsBuilder SetContent(this TBuilder builder, return builder; } - public static IInfoBarOptionsBuilder SetActionButtonContent(this TBuilder builder, string? buttonContent) + public static IInfoBarOptionsBuilder SetActionButtonContent(this TBuilder builder, [LocalizationRequired] string? buttonContent) where TBuilder : IInfoBarOptionsBuilder { builder.Configure(builder => builder.Options.ActionButtonContent = buttonContent); @@ -56,4 +57,4 @@ public static IInfoBarOptionsBuilder SetDelay(this TBuilder builder, i builder.Configure(builder => builder.Options.MilliSecondsDelay = milliSeconds); return builder; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarServiceExtension.cs index 3f3803c71a..d0b3fe66cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarServiceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Notification/InfoBarServiceExtension.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using JetBrains.Annotations; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core.Abstraction; @@ -8,17 +9,17 @@ namespace Snap.Hutao.Service.Notification; internal static class InfoBarServiceExtension { - public static Void Information(this IInfoBarService infoBarService, string message, int milliSeconds = 5000) + public static Void Information(this IInfoBarService infoBarService, [LocalizationRequired] string message, int milliSeconds = 5000) { return infoBarService.Information(builder => builder.SetMessage(message).SetDelay(milliSeconds)); } - public static Void Information(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000) + public static Void Information(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, int milliSeconds = 5000) { return infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds)); } - public static Void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 5000) + public static Void Information(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, [LocalizationRequired] string buttonContent, ICommand buttonCommand, int milliSeconds = 5000) { return infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetActionButtonContent(buttonContent).SetActionButtonCommand(buttonCommand).SetDelay(milliSeconds)); } @@ -29,12 +30,12 @@ public static Void Information(this IInfoBarService infoBarService, Action builder.SetMessage(message).SetDelay(milliSeconds)); } - public static Void Success(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000) + public static Void Success(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, int milliSeconds = 5000) { return infoBarService.Success(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds)); } @@ -45,17 +46,17 @@ public static Void Success(this IInfoBarService infoBarService, Action builder.SetMessage(message).SetDelay(milliSeconds)); } - public static Void Warning(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 30000) + public static Void Warning(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, int milliSeconds = 30000) { return infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds)); } - public static Void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 30000) + public static Void Warning(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, [LocalizationRequired] string buttonContent, ICommand buttonCommand, int milliSeconds = 30000) { return infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetActionButtonContent(buttonContent).SetActionButtonCommand(buttonCommand).SetDelay(milliSeconds)); } @@ -66,17 +67,17 @@ public static Void Warning(this IInfoBarService infoBarService, Action builder.SetMessage(message).SetDelay(milliSeconds)); } - public static Void Error(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 0) + public static Void Error(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, int milliSeconds = 0) { return infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds)); } - public static Void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 0) + public static Void Error(this IInfoBarService infoBarService, [LocalizationRequired] string title, [LocalizationRequired] string message, [LocalizationRequired] string buttonContent, ICommand buttonCommand, int milliSeconds = 0) { return infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetActionButtonContent(buttonContent).SetActionButtonCommand(buttonCommand).SetDelay(milliSeconds)); } @@ -86,12 +87,12 @@ public static Void Error(this IInfoBarService infoBarService, Exception ex, int return infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage(ex.Message).SetDelay(milliSeconds)); } - public static Void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, int milliSeconds = 0) + public static Void Error(this IInfoBarService infoBarService, Exception ex, [LocalizationRequired] string subtitle, int milliSeconds = 0) { return infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetDelay(milliSeconds)); } - public static Void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, string buttonContent, ICommand buttonCommand, int milliSeconds = 0) + public static Void Error(this IInfoBarService infoBarService, Exception ex, [LocalizationRequired] string subtitle, [LocalizationRequired] string buttonContent, ICommand buttonCommand, int milliSeconds = 0) { return infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetActionButtonContent(buttonContent).SetActionButtonCommand(buttonCommand).SetDelay(milliSeconds)); } @@ -101,4 +102,4 @@ public static Void Error(this IInfoBarService infoBarService, Action builder.SetSeverity(InfoBarSeverity.Error).Configure(configure)); return default; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs index 644ab844bd..3bde7cb988 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs @@ -11,6 +11,7 @@ using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Service.Notification; using System.IO; +using PackageOperationGameFileSystem = Snap.Hutao.Service.Game.Package.Advanced.PackageOperationGameFileSystem; namespace Snap.Hutao.UI.Xaml.View.Dialog; @@ -29,7 +30,7 @@ internal sealed partial class LaunchGameInstallGameDialog : ContentDialog private readonly IContentDialogFactory contentDialogFactory; private readonly IInfoBarService infoBarService; - public async ValueTask> GetGameFileSystemAsync() + public async ValueTask> GetGameInstallOptionsAsync() { ContentDialogResult result = await contentDialogFactory.EnqueueAndShowAsync(this).ShowTask.ConfigureAwait(false); if (result is not ContentDialogResult.Primary) @@ -41,25 +42,25 @@ public async ValueTask> GetGameFileSystemA if (string.IsNullOrWhiteSpace(GameDirectory)) { - infoBarService.Error("安装路径未选择"); + infoBarService.Error("未选择安装路径"); return new(false, default!); } if (SelectedScheme is null) { - infoBarService.Error("游戏区服未选择"); + infoBarService.Error("未选择游戏区服"); return new(false, default!); } if (!Chinese && !English && !Japanese && !Korean) { - infoBarService.Error("语音包未选择"); + infoBarService.Error("未选择语音包"); return new(false, default!); } GameAudioSystem gameAudioSystem = new(Chinese, English, Japanese, Korean); string gamePath = Path.Combine(GameDirectory, SelectedScheme.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName); - return new(true, new(new(gamePath, gameAudioSystem), SelectedScheme)); + return new(true, new(new PackageOperationGameFileSystem(gamePath, gameAudioSystem), SelectedScheme)); } private static void OnGameDirectoryChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) @@ -79,4 +80,4 @@ private void PickGameDirectory() GameDirectory = gameDirectory; } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs index 16b8d65e81..fc49847020 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs @@ -62,14 +62,14 @@ private async Task StartAsync() LaunchGameInstallGameDialog dialog = await contentDialogFactory.CreateInstanceAsync().ConfigureAwait(false); dialog.KnownSchemes = KnownLaunchSchemes.Get(); dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly); - (bool isOk, GameInstallOptions gameInstallOptions) = await dialog.GetGameFileSystemAsync().ConfigureAwait(false); + (bool isOk, GameInstallOptions gameInstallOptions) = await dialog.GetGameInstallOptionsAsync().ConfigureAwait(false); if (!isOk) { return; } - (GameFileSystem gameFileSystem, LaunchScheme launchScheme) = gameInstallOptions; + (IGameFileSystem gameFileSystem, LaunchScheme launchScheme) = gameInstallOptions; GameBranchesWrapper? branchesWrapper; GameChannelSDKsWrapper? channelSDKsWrapper; @@ -111,4 +111,4 @@ private async Task StartAsync() // Operation canceled } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index fa55e8a693..e45695deb1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -138,7 +138,14 @@ public GamePathEntry? SelectedGamePathEntry return; } - launchOptions.GamePath = value?.Path ?? string.Empty; + if (launchOptions.GamePathLock.TryWriterLock(out AsyncReaderWriterLock.Releaser releaser)) + { + using (releaser) + { + launchOptions.GamePath = value?.Path ?? string.Empty; + } + } + GamePathSelectedAndValid = File.Exists(launchOptions.GamePath); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index 3379c17c76..d57eb4058b 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -433,7 +433,7 @@ private async Task ExtractGameExeAsync() if (result is ContentDialogResult.Primary) { - GameFileSystem gameFileSystem = new(Path.Combine(extractDirectory, ExtractExeOptions.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName)); + IGameFileSystem gameFileSystem = new PackageOperationGameFileSystem(Path.Combine(extractDirectory, ExtractExeOptions.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName)); GamePackageOperationContext context = new( serviceProvider, From a49a203c234cf922566396bd0b13f4d68d821ca3 Mon Sep 17 00:00:00 2001 From: qhy040404 Date: Mon, 25 Nov 2024 20:38:30 +0800 Subject: [PATCH 2/6] disposable scope --- .../GameChannelOptionsService.cs | 49 ++++++------- .../Game/Launching/LaunchExecutionContext.cs | 8 ++- .../LaunchGameInstallGameDialog.xaml.cs | 1 - .../ViewModel/Game/GamePackageViewModel.cs | 63 +++++++++-------- .../Game/LaunchGameLaunchExecution.cs | 12 ++-- .../ViewModel/Game/LaunchGameShared.cs | 31 +++++---- .../ViewModel/Game/LaunchGameViewModel.cs | 7 +- .../Snap.Hutao/ViewModel/TestViewModel.cs | 69 ++++++++++--------- 8 files changed, 134 insertions(+), 106 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index ad6e72fc94..33feeb0c0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -21,35 +21,38 @@ public ChannelOptions GetChannelOptions() return ChannelOptions.GamePathNullOrEmpty(); } - bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); - - if (!File.Exists(gameFileSystem.GameConfigFilePath)) + using (gameFileSystem) { - // Try restore the configuration file if it does not exist - // The configuration file may be deleted by a incompatible launcher - gameConfigurationFileService.Restore(gameFileSystem.GameConfigFilePath); - } + bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); - if (!File.Exists(gameFileSystem.ScriptVersionFilePath)) - { - // Try to fix ScriptVersion by reading game_version from the configuration file - // Will check the configuration file first - // If the configuration file and ScriptVersion file are both missing, the game content is corrupted - if (!gameFileSystem.TryFixScriptVersion()) + if (!File.Exists(gameFileSystem.GameConfigFilePath)) { - return ChannelOptions.GameContentCorrupted(gameFileSystem.GameDirectory); + // Try restore the configuration file if it does not exist + // The configuration file may be deleted by a incompatible launcher + gameConfigurationFileService.Restore(gameFileSystem.GameConfigFilePath); } - } - if (!File.Exists(gameFileSystem.GameConfigFilePath)) - { - return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath); - } + if (!File.Exists(gameFileSystem.ScriptVersionFilePath)) + { + // Try to fix ScriptVersion by reading game_version from the configuration file + // Will check the configuration file first + // If the configuration file and ScriptVersion file are both missing, the game content is corrupted + if (!gameFileSystem.TryFixScriptVersion()) + { + return ChannelOptions.GameContentCorrupted(gameFileSystem.GameDirectory); + } + } + + if (!File.Exists(gameFileSystem.GameConfigFilePath)) + { + return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath); + } - List parameters = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).OfType().ToList(); - string? channel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.ChannelName)?.Value; - string? subChannel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.SubChannelName)?.Value; + List parameters = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).OfType().ToList(); + string? channel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.ChannelName)?.Value; + string? subChannel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.SubChannelName)?.Value; - return new(channel, subChannel, isOversea); + return new(channel, subChannel, isOversea); + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs index 03b6939a56..cd516c2fc5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs @@ -11,7 +11,7 @@ namespace Snap.Hutao.Service.Game.Launching; [ConstructorGenerated] -internal sealed partial class LaunchExecutionContext +internal sealed partial class LaunchExecutionContext : IDisposable { private readonly ILogger logger; @@ -84,6 +84,12 @@ public void UpdateGamePathEntry() ViewModel.SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, selectedEntry); // invalidate game file system + gameFileSystem?.Dispose(); gameFileSystem = null; } + + public void Dispose() + { + gameFileSystem?.Dispose(); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs index 3bde7cb988..8fc6c0b0fc 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs @@ -11,7 +11,6 @@ using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Service.Notification; using System.IO; -using PackageOperationGameFileSystem = Snap.Hutao.Service.Game.Package.Advanced.PackageOperationGameFileSystem; namespace Snap.Hutao.UI.Xaml.View.Dialog; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs index 01dd0d0c41..ad01d9abd3 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs @@ -105,15 +105,18 @@ public bool IsPredownloadFinished return false; } - if (!File.Exists(gameFileSystem.PredownloadStatusPath)) + using (gameFileSystem) { - return false; - } - - if (JsonSerializer.Deserialize(File.ReadAllText(gameFileSystem.PredownloadStatusPath)) is { } predownloadStatus) - { - int fileCount = Directory.GetFiles(gameFileSystem.ChunksDirectory).Length - 1; - return predownloadStatus.Finished && fileCount == predownloadStatus.TotalBlocks; + if (!File.Exists(gameFileSystem.PredownloadStatusPath)) + { + return false; + } + + if (JsonSerializer.Deserialize(File.ReadAllText(gameFileSystem.PredownloadStatusPath)) is { } predownloadStatus) + { + int fileCount = Directory.GetFiles(gameFileSystem.ChunksDirectory).Length - 1; + return predownloadStatus.Finished && fileCount == predownloadStatus.TotalBlocks; + } } return false; @@ -160,14 +163,17 @@ protected override async ValueTask LoadOverrideAsync() return true; } - if (gameFileSystem.TryGetGameVersion(out string? localVersion)) - { - LocalVersion = new(localVersion); - } - - if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.PredownloadStatusPath)) + using (gameFileSystem) { - File.Delete(gameFileSystem.PredownloadStatusPath); + if (gameFileSystem.TryGetGameVersion(out string? localVersion)) + { + LocalVersion = new(localVersion); + } + + if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.PredownloadStatusPath)) + { + File.Delete(gameFileSystem.PredownloadStatusPath); + } } return true; @@ -211,19 +217,22 @@ private async Task StartAsync(string state) GameChannelSDK? gameChannelSDK = channelSDKsWrapper.GameChannelSDKs.FirstOrDefault(sdk => sdk.Game.Id == targetLaunchScheme.GameId); - GamePackageOperationContext context = new( - serviceProvider, - operationKind, - gameFileSystem, - GameBranch.Main.GetTaggedCopy(LocalVersion.ToString()), - operationKind is GamePackageOperationKind.Predownload ? GameBranch.PreDownload : GameBranch.Main, - gameChannelSDK, - default); - - if (!await gamePackageService.StartOperationAsync(context).ConfigureAwait(false)) + using (gameFileSystem) { - // Operation canceled - return; + GamePackageOperationContext context = new( + serviceProvider, + operationKind, + gameFileSystem, + GameBranch.Main.GetTaggedCopy(LocalVersion.ToString()), + operationKind is GamePackageOperationKind.Predownload ? GameBranch.PreDownload : GameBranch.Main, + gameChannelSDK, + default); + + if (!await gamePackageService.StartOperationAsync(context).ConfigureAwait(false)) + { + // Operation canceled + return; + } } await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs index e3a6f0979b..237ce5c6f2 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs @@ -20,12 +20,14 @@ public static async ValueTask LaunchExecutionAsync(this IViewModelSupportLaunchE try { - LaunchExecutionContext context = new(scope.ServiceProvider, launchExecution, targetScheme, launchExecution.SelectedGameAccount, userAndUid); - LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false); - - if (result.Kind is not LaunchExecutionResultKind.Ok) + using (LaunchExecutionContext context = new(scope.ServiceProvider, launchExecution, targetScheme, launchExecution.SelectedGameAccount, userAndUid)) { - infoBarService.Warning(result.ErrorMessage); + LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false); + + if (result.Kind is not LaunchExecutionResultKind.Ok) + { + infoBarService.Warning(result.ErrorMessage); + } } } catch (Exception ex) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs index 86096d4d24..1b0c7ad99d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs @@ -77,24 +77,27 @@ private async Task HandleConfigurationFileNotFoundAsync() return; } - bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); + using (gameFileSystem) + { + bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); - LaunchGameConfigurationFixDialog dialog = await contentDialogFactory - .CreateInstanceAsync() - .ConfigureAwait(false); + LaunchGameConfigurationFixDialog dialog = await contentDialogFactory + .CreateInstanceAsync() + .ConfigureAwait(false); - await taskContext.SwitchToMainThreadAsync(); - dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea); - dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly); - (bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false); + await taskContext.SwitchToMainThreadAsync(); + dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea); + dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly); + (bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false); - if (!isOk) - { - return; - } + if (!isOk) + { + return; + } - gameFileSystem.TryFixConfigurationFile(launchScheme); - infoBarService.Success(SH.ViewModelLaunchGameFixConfigurationFileSuccess); + gameFileSystem.TryFixConfigurationFile(launchScheme); + infoBarService.Success(SH.ViewModelLaunchGameFixConfigurationFileSuccess); + } } [Command("HandleGamePathNullOrEmptyCommand")] diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index e45695deb1..dc0ea966a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -275,8 +275,11 @@ private async Task OpenScreenshotFolderAsync() return; } - Directory.CreateDirectory(gameFileSystem.ScreenShotDirectory); - await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory); + using (gameFileSystem) + { + Directory.CreateDirectory(gameFileSystem.ScreenShotDirectory); + await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory); + } } [SuppressMessage("", "SH003")] diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index d57eb4058b..8e7d6e208f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -345,46 +345,49 @@ private async Task ExtractGameBlocksAsync() return; } - if (!gameFileSystem.TryGetGameVersion(out string? localVersion)) + using (gameFileSystem) { - return; - } + if (!gameFileSystem.TryGetGameVersion(out string? localVersion)) + { + return; + } - (bool isOk, string? extractDirectory) = fileSystemPickerInteraction.PickFolder("Select directory to extract the game blks"); - if (!isOk) - { - return; - } + (bool isOk, string? extractDirectory) = fileSystemPickerInteraction.PickFolder("Select directory to extract the game blks"); + if (!isOk) + { + return; + } - string message = $""" - Local: {localVersion} - Remote: {gameBranch.PreDownload.Tag} - Extract Directory: {extractDirectory} - - Please ensure local game is integrated. - We need some of old blocks to patch up. - """; + string message = $""" + Local: {localVersion} + Remote: {gameBranch.PreDownload.Tag} + Extract Directory: {extractDirectory} - ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync( - "Extract Game Blocks", - message) - .ConfigureAwait(false); + Please ensure local game is integrated. + We need some of old blocks to patch up. + """; - if (result is not ContentDialogResult.Primary) - { - return; - } + ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync( + "Extract Game Blocks", + message) + .ConfigureAwait(false); + + if (result is not ContentDialogResult.Primary) + { + return; + } - GamePackageOperationContext context = new( - serviceProvider, - GamePackageOperationKind.ExtractBlk, - gameFileSystem, - gameBranch.Main.GetTaggedCopy(localVersion), - gameBranch.PreDownload, - default, - extractDirectory); + GamePackageOperationContext context = new( + serviceProvider, + GamePackageOperationKind.ExtractBlk, + gameFileSystem, + gameBranch.Main.GetTaggedCopy(localVersion), + gameBranch.PreDownload, + default, + extractDirectory); - await gamePackageService.StartOperationAsync(context).ConfigureAwait(false); + await gamePackageService.StartOperationAsync(context).ConfigureAwait(false); + } } [Command("ExtractGameExeCommand")] From 06a3886b388cdf3987cd61f60e1e35a8939039ff Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 25 Nov 2024 22:36:31 +0800 Subject: [PATCH 3/6] code style --- src/Snap.Hutao/.editorconfig | 2 +- .../GameChannelOptionsService.cs | 2 +- ...aunchExecutionEnsureGameResourceHandler.cs | 6 +-- ...ecutionGameProcessInitializationHandler.cs | 4 +- ...LaunchExecutionSetChannelOptionsHandler.cs | 2 +- .../LaunchExecutionUnlockFpsHandler.cs | 2 +- .../Game/Launching/LaunchExecutionContext.cs | 13 +++-- .../Package/Advanced/GamePackageService.cs | 2 +- .../Package/Advanced/IGamePackageService.cs | 2 +- .../Game/Package/PackageConverterContext.cs | 6 +-- .../Game/RestrictedGamePathAccessExtension.cs | 4 +- .../Game/GamePackageInstallViewModel.cs | 2 +- .../ViewModel/Game/GamePackageViewModel.cs | 52 +++++++++---------- .../ViewModel/Game/LaunchGameShared.cs | 2 +- .../ViewModel/Game/LaunchGameViewModel.cs | 2 +- .../Snap.Hutao/ViewModel/TestViewModel.cs | 18 +++---- 16 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig index e6469f69b8..f5cd40612b 100644 --- a/src/Snap.Hutao/.editorconfig +++ b/src/Snap.Hutao/.editorconfig @@ -274,7 +274,7 @@ dotnet_diagnostic.CA1849.severity = suggestion dotnet_diagnostic.CA1852.severity = suggestion # CA2000: 丢失范围之前释放对象 -dotnet_diagnostic.CA2000.severity = suggestion +dotnet_diagnostic.CA2000.severity = none # CA2002: 不要锁定具有弱标识的对象 dotnet_diagnostic.CA2002.severity = suggestion diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index 33feeb0c0d..1e6f303eac 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -16,7 +16,7 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer public ChannelOptions GetChannelOptions() { - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return ChannelOptions.GamePathNullOrEmpty(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs index 6781c715ee..94b4623450 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs @@ -26,7 +26,7 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio { public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next) { - if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!context.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } @@ -60,7 +60,7 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx await next().ConfigureAwait(false); } - private static bool ShouldConvert(LaunchExecutionContext context, GameFileSystem gameFileSystem) + private static bool ShouldConvert(LaunchExecutionContext context, IGameFileSystem gameFileSystem) { // Configuration file changed if (context.ChannelOptionsChanged) @@ -86,7 +86,7 @@ private static bool ShouldConvert(LaunchExecutionContext context, GameFileSystem return false; } - private static async ValueTask EnsureGameResourceAsync(LaunchExecutionContext context, GameFileSystem gameFileSystem, IProgress progress) + private static async ValueTask EnsureGameResourceAsync(LaunchExecutionContext context, IGameFileSystem gameFileSystem, IProgress progress) { string gameFolder = gameFileSystem.GameDirectory; context.Logger.LogInformation("Game folder: {GameFolder}", gameFolder); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs index 751313d042..2a4cd88f5f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs @@ -9,7 +9,7 @@ internal sealed class LaunchExecutionGameProcessInitializationHandler : ILaunchE { public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next) { - if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!context.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } @@ -21,7 +21,7 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx } } - private static System.Diagnostics.Process InitializeGameProcess(LaunchExecutionContext context, GameFileSystem gameFileSystem) + private static System.Diagnostics.Process InitializeGameProcess(LaunchExecutionContext context, IGameFileSystem gameFileSystem) { LaunchOptions launchOptions = context.Options; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs index c705c9bc5b..584eca5bf6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs @@ -11,7 +11,7 @@ internal sealed class LaunchExecutionSetChannelOptionsHandler : ILaunchExecution { public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next) { - if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!context.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { // context.Result is set in TryGetGameFileSystem return; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionUnlockFpsHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionUnlockFpsHandler.cs index e22fdfc552..d420f69bd5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionUnlockFpsHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionUnlockFpsHandler.cs @@ -16,7 +16,7 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx context.Logger.LogInformation("Unlocking FPS"); context.Progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps)); - if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!context.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs index cd516c2fc5..eda0350e8c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/LaunchExecutionContext.cs @@ -15,7 +15,7 @@ internal sealed partial class LaunchExecutionContext : IDisposable { private readonly ILogger logger; - private GameFileSystem? gameFileSystem; + private IGameFileSystem? gameFileSystem; [SuppressMessage("", "SH007")] public LaunchExecutionContext(IServiceProvider serviceProvider, IViewModelSupportLaunchExecution viewModel, LaunchScheme? targetScheme, GameAccount? account, UserAndUid? userAndUid) @@ -58,9 +58,8 @@ public LaunchExecutionContext(IServiceProvider serviceProvider, IViewModelSuppor public System.Diagnostics.Process Process { get; set; } = default!; - public bool TryGetGameFileSystem([NotNullWhen(true)] out GameFileSystem? gameFileSystem) + public bool TryGetGameFileSystem([NotNullWhen(true)] out IGameFileSystem? gameFileSystem) { - // TODO: for safety reasons, we should lock the game file path somehow, when we acquired the game file system if (this.gameFileSystem is not null) { gameFileSystem = this.gameFileSystem; @@ -80,12 +79,12 @@ public bool TryGetGameFileSystem([NotNullWhen(true)] out GameFileSystem? gameFil public void UpdateGamePathEntry() { - ImmutableArray gamePathEntries = Options.GetGamePathEntries(out GamePathEntry? selectedEntry); - ViewModel.SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, selectedEntry); - - // invalidate game file system + // Invalidate game file system gameFileSystem?.Dispose(); gameFileSystem = null; + + ImmutableArray gamePathEntries = Options.GetGamePathEntries(out GamePathEntry? selectedEntry); + ViewModel.SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, selectedEntry); } public void Dispose() diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs index 7f26c77e14..538411ac36 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs @@ -45,7 +45,7 @@ internal sealed partial class GamePackageService : IGamePackageService private CancellationTokenSource? operationCts; private TaskCompletionSource? operationTcs; - public async ValueTask StartOperationAsync(GamePackageOperationContext operationContext) + public async ValueTask ExecuteOperationAsync(GamePackageOperationContext operationContext) { await CancelOperationAsync().ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/IGamePackageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/IGamePackageService.cs index de19c4e316..66abed0ddc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/IGamePackageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/IGamePackageService.cs @@ -5,7 +5,7 @@ namespace Snap.Hutao.Service.Game.Package.Advanced; internal interface IGamePackageService { - ValueTask StartOperationAsync(GamePackageOperationContext context); + ValueTask ExecuteOperationAsync(GamePackageOperationContext context); ValueTask CancelOperationAsync(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs index 79da2872a6..cb1eca9257 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs @@ -84,7 +84,7 @@ private PackageConverterContext(CommonReferences common) public LaunchScheme TargetScheme { get => Common.TargetScheme; } - public GameFileSystem GameFileSystem { get => Common.GameFileSystem; } + public IGameFileSystem GameFileSystem { get => Common.GameFileSystem; } public GameChannelSDK? GameChannelSDK { get => Common.GameChannelSDK; } @@ -123,7 +123,7 @@ internal readonly struct CommonReferences public readonly HttpClient HttpClient; public readonly LaunchScheme CurrentScheme; public readonly LaunchScheme TargetScheme; - public readonly GameFileSystem GameFileSystem; + public readonly IGameFileSystem GameFileSystem; public readonly GameChannelSDK? GameChannelSDK; public readonly DeprecatedFilesWrapper? DeprecatedFiles; public readonly IProgress Progress; @@ -132,7 +132,7 @@ public CommonReferences( HttpClient httpClient, LaunchScheme currentScheme, LaunchScheme targetScheme, - GameFileSystem gameFileSystem, + IGameFileSystem gameFileSystem, GameChannelSDK? gameChannelSDK, DeprecatedFilesWrapper? deprecatedFiles, IProgress progress) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs index 6556ef49ff..e8fa78d1e6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs @@ -9,7 +9,7 @@ namespace Snap.Hutao.Service.Game; internal static class RestrictedGamePathAccessExtension { - public static bool TryGetGameFileSystem(this IRestrictedGamePathAccess access, [NotNullWhen(true)] out GameFileSystem? fileSystem) + public static bool TryGetGameFileSystem(this IRestrictedGamePathAccess access, [NotNullWhen(true)] out IGameFileSystem? fileSystem) { string gamePath = access.GamePath; @@ -25,7 +25,7 @@ public static bool TryGetGameFileSystem(this IRestrictedGamePathAccess access, [ return false; } - fileSystem = new(gamePath, releaser); + fileSystem = new GameFileSystem(gamePath, releaser); return true; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs index fc49847020..d3583ec321 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageInstallViewModel.cs @@ -106,7 +106,7 @@ private async Task StartAsync() gameFileSystem.GenerateConfigurationFile(branch.Main.Tag, launchScheme); - if (!await gamePackageService.StartOperationAsync(context).ConfigureAwait(false)) + if (!await gamePackageService.ExecuteOperationAsync(context).ConfigureAwait(false)) { // Operation canceled } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs index ad01d9abd3..1cd4ca5261 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs @@ -100,7 +100,7 @@ public bool IsPredownloadFinished { get { - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return false; } @@ -125,9 +125,9 @@ public bool IsPredownloadFinished public async ValueTask ForceLoadAsync() { - await LoadOverrideAsync().ConfigureAwait(false); + bool result = await LoadOverrideAsync().ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); - IsInitialized = true; + IsInitialized = result; } protected override async ValueTask LoadOverrideAsync() @@ -149,37 +149,37 @@ protected override async ValueTask LoadOverrideAsync() } } - if (branchesWrapper.GameBranches.FirstOrDefault(b => b.Game.Id == launchScheme.GameId) is { } branch) + if (branchesWrapper.GameBranches.FirstOrDefault(b => b.Game.Id == launchScheme.GameId) is not { } branch) { - await taskContext.SwitchToMainThreadAsync(); - GameBranch = branch; - LaunchScheme = launchScheme; + return false; + } - RemoteVersion = new(branch.Main.Tag); - PreVersion = branch.PreDownload is { Tag: { } tag } ? new(tag) : default; + await taskContext.SwitchToMainThreadAsync(); + GameBranch = branch; + LaunchScheme = launchScheme; - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + RemoteVersion = new(branch.Main.Tag); + PreVersion = branch.PreDownload is { Tag: { } tag } ? new(tag) : default; + + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) + { + return false; + } + + using (gameFileSystem) + { + if (gameFileSystem.TryGetGameVersion(out string? localVersion)) { - return true; + LocalVersion = new(localVersion); } - using (gameFileSystem) + if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.PredownloadStatusPath)) { - if (gameFileSystem.TryGetGameVersion(out string? localVersion)) - { - LocalVersion = new(localVersion); - } - - if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.PredownloadStatusPath)) - { - File.Delete(gameFileSystem.PredownloadStatusPath); - } + File.Delete(gameFileSystem.PredownloadStatusPath); } - - return true; } - return false; + return true; } [Command("StartCommand")] @@ -192,7 +192,7 @@ private async Task StartAsync(string state) GamePackageOperationKind operationKind = Enum.Parse(state); - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } @@ -228,7 +228,7 @@ private async Task StartAsync(string state) gameChannelSDK, default); - if (!await gamePackageService.StartOperationAsync(context).ConfigureAwait(false)) + if (!await gamePackageService.ExecuteOperationAsync(context).ConfigureAwait(false)) { // Operation canceled return; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs index 1b0c7ad99d..e3f8425a9b 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs @@ -72,7 +72,7 @@ internal sealed partial class LaunchGameShared [Command("HandleConfigurationFileNotFoundCommand")] private async Task HandleConfigurationFileNotFoundAsync() { - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index dc0ea966a1..a9ecabeecd 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -270,7 +270,7 @@ private async Task RemoveGameAccountAsync(GameAccount? gameAccount) [Command("OpenScreenshotFolderCommand")] private async Task OpenScreenshotFolderAsync() { - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index 8e7d6e208f..1f563306f8 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -340,7 +340,7 @@ private async Task ExtractGameBlocksAsync() return; } - if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) + if (!launchOptions.TryGetGameFileSystem(out IGameFileSystem? gameFileSystem)) { return; } @@ -359,13 +359,13 @@ private async Task ExtractGameBlocksAsync() } string message = $""" - Local: {localVersion} - Remote: {gameBranch.PreDownload.Tag} - Extract Directory: {extractDirectory} + Local: {localVersion} + Remote: {gameBranch.PreDownload.Tag} + Extract Directory: {extractDirectory} - Please ensure local game is integrated. - We need some of old blocks to patch up. - """; + Please ensure local game is integrated. + We need some of old blocks to patch up. + """; ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync( "Extract Game Blocks", @@ -386,7 +386,7 @@ We need some of old blocks to patch up. default, extractDirectory); - await gamePackageService.StartOperationAsync(context).ConfigureAwait(false); + await gamePackageService.ExecuteOperationAsync(context).ConfigureAwait(false); } } @@ -447,7 +447,7 @@ private async Task ExtractGameExeAsync() default, default); - await gamePackageService.StartOperationAsync(context).ConfigureAwait(false); + await gamePackageService.ExecuteOperationAsync(context).ConfigureAwait(false); } } From 951b15c7b26027c168a2a63448151e9c43a71653 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 26 Nov 2024 14:13:31 +0800 Subject: [PATCH 4/6] refine ini serialization --- .../Snap.Hutao/Core/IO/Ini/IniParameter.cs | 2 +- .../Snap.Hutao/Core/IO/Ini/IniSerializer.cs | 19 +++++++------ .../GameChannelOptionsService.cs | 28 +++++++++++++++++-- .../Snap.Hutao/Service/Game/GameFileSystem.cs | 22 +++++++-------- .../Service/Game/GameFileSystemExtension.cs | 23 +++++++++++---- ...LaunchExecutionSetChannelOptionsHandler.cs | 5 ++-- .../Game/Locator/RegistryLauncherLocator.cs | 11 ++++---- .../Package/Advanced/GamePackageService.cs | 2 +- 8 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs index 0f31d2e6c2..3a9718ed22 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniParameter.cs @@ -30,4 +30,4 @@ public override string ToString() { return $"{Key}={Value}"; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs index b703b2cdeb..4cb8007b5f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/Ini/IniSerializer.cs @@ -1,13 +1,14 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Collections.Immutable; using System.IO; namespace Snap.Hutao.Core.IO.Ini; internal static class IniSerializer { - public static List DeserializeFromFile(string filePath) + public static ImmutableArray DeserializeFromFile(string filePath) { using (StreamReader reader = File.OpenText(filePath)) { @@ -15,9 +16,9 @@ public static List DeserializeFromFile(string filePath) } } - public static List Deserialize(Stream fileStream) + public static ImmutableArray Deserialize(Stream stream) { - using (StreamReader reader = new(fileStream)) + using (StreamReader reader = new(stream)) { return DeserializeCore(reader); } @@ -39,9 +40,9 @@ public static void Serialize(FileStream fileStream, IEnumerable elem } } - private static List DeserializeCore(StreamReader reader) + private static ImmutableArray DeserializeCore(StreamReader reader) { - List results = []; + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); IniSection? currentSection = default; while (reader.ReadLine() is { } line) @@ -56,26 +57,26 @@ private static List DeserializeCore(StreamReader reader) if (lineSpan[0] is '[') { IniSection section = new(lineSpan[1..^1].ToString()); - results.Add(section); + builder.Add(section); currentSection = section; } if (lineSpan[0] is ';') { IniComment comment = new(lineSpan[1..].ToString()); - results.Add(comment); + builder.Add(comment); currentSection?.Children.Add(comment); } if (lineSpan.TrySplitIntoTwo('=', out ReadOnlySpan left, out ReadOnlySpan right)) { IniParameter parameter = new(left.Trim().ToString(), right.Trim().ToString()); - results.Add(parameter); + builder.Add(parameter); currentSection?.Children.Add(parameter); } } - return results; + return builder.ToImmutable(); } private static void SerializeCore(StreamWriter writer, IEnumerable elements) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index 1e6f303eac..c5119186d1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -4,6 +4,7 @@ using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Service.Game.Scheme; using System.IO; +using System.Runtime.CompilerServices; namespace Snap.Hutao.Service.Game.Configuration; @@ -48,9 +49,30 @@ public ChannelOptions GetChannelOptions() return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath); } - List parameters = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).OfType().ToList(); - string? channel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.ChannelName)?.Value; - string? subChannel = parameters.FirstOrDefault(p => p.Key is ChannelOptions.SubChannelName)?.Value; + string? channel = default; + string? subChannel = default; + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) + { + if (element is not IniParameter parameter) + { + continue; + } + + switch (parameter.Key) + { + case ChannelOptions.ChannelName: + channel = parameter.Value; + break; + case ChannelOptions.SubChannelName: + subChannel = parameter.Value; + break; + } + + if (channel is not null && subChannel is not null) + { + break; + } + } return new(channel, subChannel, isOversea); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs index 72bc21d087..860e712583 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs @@ -16,13 +16,6 @@ public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releas this.releaser = releaser; } - public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releaser, GameAudioSystem gameAudioSystem) - { - GameFilePath = gameFilePath; - this.releaser = releaser; - Audio = gameAudioSystem; - } - public string GameFilePath { get; } [field: MaybeNull] @@ -50,15 +43,20 @@ public string GameDirectory [field: MaybeNull] public string PCGameSDKFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } - public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); } + [field: MaybeNull] + public string ScreenShotDirectory { get => field ??= Path.Combine(GameDirectory, "ScreenShot"); } - public string DataDirectory { get => Path.Combine(GameDirectory, LaunchScheme.ExecutableIsOversea(GameFileName) ? GameConstants.GenshinImpactData : GameConstants.YuanShenData); } + [field: MaybeNull] + public string DataDirectory { get => field ??= Path.Combine(GameDirectory, LaunchScheme.ExecutableIsOversea(GameFileName) ? GameConstants.GenshinImpactData : GameConstants.YuanShenData); } - public string ScriptVersionFilePath { get => Path.Combine(DataDirectory, "Persistent", "ScriptVersion"); } + [field: MaybeNull] + public string ScriptVersionFilePath { get => field ??= Path.Combine(DataDirectory, "Persistent", "ScriptVersion"); } - public string ChunksDirectory { get => Path.Combine(GameDirectory, "chunks"); } + [field: MaybeNull] + public string ChunksDirectory { get => field ??= Path.Combine(GameDirectory, "chunks"); } - public string PredownloadStatusPath { get => Path.Combine(ChunksDirectory, "snap_hutao_predownload_status.json"); } + [field: MaybeNull] + public string PredownloadStatusPath { get => field ??= Path.Combine(ChunksDirectory, "snap_hutao_predownload_status.json"); } [field: MaybeNull] public GameAudioSystem Audio { get => field ??= new(GameFilePath); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs index 121f56afe5..72f5d33e92 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Service.Game.Scheme; +using System.Collections.Immutable; using System.IO; using System.Runtime.InteropServices; @@ -15,7 +16,7 @@ public static bool TryGetGameVersion(this IGameFileSystem gameFileSystem, [NotNu version = default!; if (File.Exists(gameFileSystem.GameConfigFilePath)) { - foreach (ref readonly IniElement element in CollectionsMarshal.AsSpan(IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath))) + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) { if (element is IniParameter { Key: "game_version" } parameter) { @@ -56,12 +57,22 @@ public static void GenerateConfigurationFile(this IGameFileSystem gameFileSystem File.WriteAllText(gameFileSystem.GameConfigFilePath, content); } - public static void UpdateConfigurationFile(this IGameFileSystem gameFileSystem, string version) + public static bool TryUpdateConfigurationFile(this IGameFileSystem gameFileSystem, string version) { - List ini = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath); - IniParameter gameVersion = (IniParameter)ini.Single(e => e is IniParameter { Key: "game_version" }); - gameVersion.Set(version); + bool updated = false; + ImmutableArray ini = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath); + foreach (ref readonly IniElement element in ini.AsSpan()) + { + if (element is not IniParameter { Key: "game_version" } parameter) + { + continue; + } + + updated = parameter.Set(version); + } + IniSerializer.SerializeToFile(gameFileSystem.GameConfigFilePath, ini); + return updated; } public static bool TryFixConfigurationFile(this IGameFileSystem gameFileSystem, LaunchScheme launchScheme) @@ -85,7 +96,7 @@ public static bool TryFixScriptVersion(this IGameFileSystem gameFileSystem) } string? version = default; - foreach (ref readonly IniElement element in CollectionsMarshal.AsSpan(IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath))) + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) { if (element is IniParameter { Key: "game_version" } parameter) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs index 584eca5bf6..21cd8a9b57 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Service.Game.Configuration; +using System.Collections.Immutable; using System.IO; namespace Snap.Hutao.Service.Game.Launching.Handler; @@ -20,10 +21,10 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx string configPath = gameFileSystem.GameConfigFilePath; context.Logger.LogInformation("Game config file path: {ConfigPath}", configPath); - List elements; + ImmutableArray elements; try { - elements = [.. IniSerializer.DeserializeFromFile(configPath)]; + elements = IniSerializer.DeserializeFromFile(configPath); } catch (FileNotFoundException) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs index 48eddcb40b..019604496c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Locator/RegistryLauncherLocator.cs @@ -16,7 +16,7 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator private readonly ITaskContext taskContext; [GeneratedRegex(@"\\x(?=[0-9a-f]{4})")] - private static partial Regex UTF16Regex { get; } + private static partial Regex Utf16Regex { get; } public async ValueTask> LocateGamePathAsync() { @@ -36,8 +36,7 @@ public async ValueTask> LocateGamePathAsync() string? escapedPath; using (FileStream stream = File.OpenRead(configPath)) { - IEnumerable elements = IniSerializer.Deserialize(stream); - escapedPath = elements + escapedPath = IniSerializer.Deserialize(stream) .OfType() .FirstOrDefault(p => p.Key == "game_install_path")?.Value; } @@ -63,13 +62,13 @@ private static ValueResult LocateInternal(string valueName) private static string Unescape(string str) { - string hex4Result = UTF16Regex.Replace(str, @"\u"); + string hex4Result = Utf16Regex.Replace(str, @"\u"); // 不包含中文 - // Some one's folder might begin with 'u' + // Someone's folder might begin with 'u' if (!hex4Result.Contains(@"\u", StringComparison.Ordinal)) { - // fix path with \ + // Fix path with \ hex4Result = hex4Result.Replace(@"\", @"\\", StringComparison.Ordinal); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs index 538411ac36..0ac2c8b7a9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs @@ -347,7 +347,7 @@ await DecodeManifestsAsync(context, context.Operation.RemoteBranch).ConfigureAwa await VerifyAndRepairCoreAsync(context, remoteBuild, remoteBuild.TotalBytes, remoteBuild.TotalChunks).ConfigureAwait(false); - context.Operation.GameFileSystem.UpdateConfigurationFile(context.Operation.RemoteBranch.Tag); + context.Operation.GameFileSystem.TryUpdateConfigurationFile(context.Operation.RemoteBranch.Tag); if (Directory.Exists(context.Operation.ProxiedChunksDirectory)) { From b5c6d29aebd7aeeeb6236575ec5fd8ec0c4718ec Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 26 Nov 2024 15:57:47 +0800 Subject: [PATCH 5/6] refactor --- .../Core/ExceptionService/HutaoException.cs | 16 ++ .../Snap.Hutao/Core/HutaoRuntime.cs | 6 +- .../GameChannelOptionsService.cs | 20 +- .../Service/Game/GameAudioSystem.cs | 33 ++-- .../Snap.Hutao/Service/Game/GameFileSystem.cs | 44 +---- .../Service/Game/GameFileSystemExtension.cs | 184 ++++++++++++++++-- .../Service/Game/IGameFileSystem.cs | 21 +- ...aunchExecutionEnsureGameResourceHandler.cs | 18 +- ...ecutionGameProcessInitializationHandler.cs | 2 +- ...LaunchExecutionSetChannelOptionsHandler.cs | 2 +- .../Package/Advanced/GameAssetOperation.cs | 12 +- .../Advanced/GamePackageOperationContext.cs | 8 +- .../Package/Advanced/GamePackageService.cs | 12 +- .../PackageOperationGameFileSystem.cs | 41 +--- .../Game/Package/PackageConverterContext.cs | 6 +- .../Package/ScatteredFilesPackageConverter.cs | 23 +-- .../Package/SophonChunksPackageConverter.cs | 21 +- .../Service/Game/Scheme/LaunchScheme.cs | 1 + .../ViewModel/Game/GamePackageViewModel.cs | 10 +- .../ViewModel/Game/LaunchGameShared.cs | 5 +- .../ViewModel/Game/LaunchGameViewModel.cs | 4 +- 21 files changed, 285 insertions(+), 204 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs index 0f42bc8db5..74a50f54e2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs @@ -88,4 +88,20 @@ public static OperationCanceledException OperationCanceled(string message, Excep { throw new OperationCanceledException(message, innerException); } + + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static ObjectDisposedException ObjectDisposed(string objectName) + { + throw new ObjectDisposedException(objectName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ObjectDisposedIf(bool condition, string objectName) + { + if (condition) + { + throw new ObjectDisposedException(objectName); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/HutaoRuntime.cs b/src/Snap.Hutao/Snap.Hutao/Core/HutaoRuntime.cs index a4eccf2160..1453713dc3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/HutaoRuntime.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/HutaoRuntime.cs @@ -86,12 +86,12 @@ private static string InitializeDataFolder() string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); #if IS_ALPHA_BUILD - string folderName = "HutaoAlpha"; + const string FolderName = "HutaoAlpha"; #else // 使得迁移能正常生成 - string folderName = "Hutao"; + const string FolderName = "Hutao"; #endif - string path = Path.GetFullPath(Path.Combine(myDocuments, folderName)); + string path = Path.GetFullPath(Path.Combine(myDocuments, FolderName)); Directory.CreateDirectory(path); return path; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs index c5119186d1..aacc51f667 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Configuration/GameChannelOptionsService.cs @@ -24,34 +24,32 @@ public ChannelOptions GetChannelOptions() using (gameFileSystem) { - bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); - - if (!File.Exists(gameFileSystem.GameConfigFilePath)) + if (!File.Exists(gameFileSystem.GetGameConfigurationFilePath())) { // Try restore the configuration file if it does not exist - // The configuration file may be deleted by a incompatible launcher - gameConfigurationFileService.Restore(gameFileSystem.GameConfigFilePath); + // The configuration file may be deleted by an incompatible launcher + gameConfigurationFileService.Restore(gameFileSystem.GetGameConfigurationFilePath()); } - if (!File.Exists(gameFileSystem.ScriptVersionFilePath)) + if (!File.Exists(gameFileSystem.GetScriptVersionFilePath())) { // Try to fix ScriptVersion by reading game_version from the configuration file // Will check the configuration file first // If the configuration file and ScriptVersion file are both missing, the game content is corrupted if (!gameFileSystem.TryFixScriptVersion()) { - return ChannelOptions.GameContentCorrupted(gameFileSystem.GameDirectory); + return ChannelOptions.GameContentCorrupted(gameFileSystem.GetGameDirectory()); } } - if (!File.Exists(gameFileSystem.GameConfigFilePath)) + if (!File.Exists(gameFileSystem.GetGameConfigurationFilePath())) { - return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath); + return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GetGameConfigurationFilePath()); } string? channel = default; string? subChannel = default; - foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GetGameConfigurationFilePath()).AsSpan()) { if (element is not IniParameter parameter) { @@ -74,7 +72,7 @@ public ChannelOptions GetChannelOptions() } } - return new(channel, subChannel, isOversea); + return new(channel, subChannel, gameFileSystem.IsOversea()); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs index fb249ca99f..b76cce6cc8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs @@ -7,35 +7,30 @@ namespace Snap.Hutao.Service.Game; internal sealed class GameAudioSystem { - private readonly string gameDirectory; - - private bool? chinese; - private bool? english; - private bool? japanese; - private bool? korean; - public GameAudioSystem(string gameFilePath) { string? directory = Path.GetDirectoryName(gameFilePath); ArgumentException.ThrowIfNullOrEmpty(directory); - gameDirectory = directory; + + Chinese = File.Exists(Path.Combine(directory, GameConstants.AudioChinesePkgVersion)); + English = File.Exists(Path.Combine(directory, GameConstants.AudioEnglishPkgVersion)); + Japanese = File.Exists(Path.Combine(directory, GameConstants.AudioJapanesePkgVersion)); + Korean = File.Exists(Path.Combine(directory, GameConstants.AudioKoreanPkgVersion)); } public GameAudioSystem(bool chinese, bool english, bool japanese, bool korean) { - gameDirectory = default!; - - this.chinese = chinese; - this.english = english; - this.japanese = japanese; - this.korean = korean; + Chinese = chinese; + English = english; + Japanese = japanese; + Korean = korean; } - public bool Chinese { get => chinese ??= File.Exists(Path.Combine(gameDirectory, GameConstants.AudioChinesePkgVersion)); } + public bool Chinese { get; } - public bool English { get => english ??= File.Exists(Path.Combine(gameDirectory, GameConstants.AudioEnglishPkgVersion)); } + public bool English { get; } - public bool Japanese { get => japanese ??= File.Exists(Path.Combine(gameDirectory, GameConstants.AudioJapanesePkgVersion)); } + public bool Japanese { get; } - public bool Korean { get => korean ??= File.Exists(Path.Combine(gameDirectory, GameConstants.AudioKoreanPkgVersion)); } -} + public bool Korean { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs index 860e712583..e1fc7c4c2b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Service.Game.Scheme; using System.IO; @@ -18,51 +19,14 @@ public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releas public string GameFilePath { get; } - [field: MaybeNull] - public string GameFileName { get => field ??= Path.GetFileName(GameFilePath); } - - [field: MaybeNull] - public string GameDirectory - { - get - { - if (field is not null) - { - return field; - } - - string? directoryName = Path.GetDirectoryName(GameFilePath); - ArgumentException.ThrowIfNullOrEmpty(directoryName); - return field = directoryName; - } - } - - [field: MaybeNull] - public string GameConfigFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); } - - [field: MaybeNull] - public string PCGameSDKFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } - - [field: MaybeNull] - public string ScreenShotDirectory { get => field ??= Path.Combine(GameDirectory, "ScreenShot"); } - - [field: MaybeNull] - public string DataDirectory { get => field ??= Path.Combine(GameDirectory, LaunchScheme.ExecutableIsOversea(GameFileName) ? GameConstants.GenshinImpactData : GameConstants.YuanShenData); } - - [field: MaybeNull] - public string ScriptVersionFilePath { get => field ??= Path.Combine(DataDirectory, "Persistent", "ScriptVersion"); } - - [field: MaybeNull] - public string ChunksDirectory { get => field ??= Path.Combine(GameDirectory, "chunks"); } - - [field: MaybeNull] - public string PredownloadStatusPath { get => field ??= Path.Combine(ChunksDirectory, "snap_hutao_predownload_status.json"); } - [field: MaybeNull] public GameAudioSystem Audio { get => field ??= new(GameFilePath); } + public bool IsDisposed { get; private set; } + public void Dispose() { releaser.Dispose(); + IsDisposed = true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs index 72f5d33e92..650294f587 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystemExtension.cs @@ -1,22 +1,176 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.Ini; using Snap.Hutao.Service.Game.Scheme; using System.Collections.Immutable; using System.IO; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Snap.Hutao.Service.Game; internal static class GameFileSystemExtension { + private static readonly ConditionalWeakTable GameFileSystemGameFileNames = []; + private static readonly ConditionalWeakTable GameFileSystemGameDirectories = []; + private static readonly ConditionalWeakTable GameFileSystemGameConfigurationFilePaths = []; + private static readonly ConditionalWeakTable GameFileSystemPcGameSdkFilePaths = []; + private static readonly ConditionalWeakTable GameFileSystemScreenShotDirectories = []; + private static readonly ConditionalWeakTable GameFileSystemDataDirectories = []; + private static readonly ConditionalWeakTable GameFileSystemScriptVersionFilePaths = []; + private static readonly ConditionalWeakTable GameFileSystemChunksDirectories = []; + private static readonly ConditionalWeakTable GameFileSystemPredownloadStatusPaths = []; + + public static string GetGameFileName(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemGameFileNames.TryGetValue(gameFileSystem, out string? gameFileName)) + { + return gameFileName; + } + + gameFileName = string.Intern(Path.GetFileName(gameFileSystem.GameFilePath)); + GameFileSystemGameFileNames.Add(gameFileSystem, gameFileName); + return gameFileName; + } + + public static string GetGameDirectory(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemGameDirectories.TryGetValue(gameFileSystem, out string? gameDirectory)) + { + return gameDirectory; + } + + gameDirectory = Path.GetDirectoryName(gameFileSystem.GameFilePath); + ArgumentException.ThrowIfNullOrEmpty(gameDirectory); + string internedGameDirectory = string.Intern(gameDirectory); + GameFileSystemGameDirectories.Add(gameFileSystem, internedGameDirectory); + return internedGameDirectory; + } + + public static string GetGameConfigurationFilePath(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemGameConfigurationFilePaths.TryGetValue(gameFileSystem, out string? gameConfigFilePath)) + { + return gameConfigFilePath; + } + + gameConfigFilePath = string.Intern(Path.Combine(gameFileSystem.GetGameDirectory(), GameConstants.ConfigFileName)); + GameFileSystemGameConfigurationFilePaths.Add(gameFileSystem, gameConfigFilePath); + return gameConfigFilePath; + } + + public static string GetPcGameSdkFilePath(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + // ReSharper disable once InconsistentNaming + if (GameFileSystemPcGameSdkFilePaths.TryGetValue(gameFileSystem, out string? pcGameSdkFilePath)) + { + return pcGameSdkFilePath; + } + + pcGameSdkFilePath = string.Intern(Path.Combine(gameFileSystem.GetGameDirectory(), GameConstants.PCGameSDKFilePath)); + GameFileSystemPcGameSdkFilePaths.Add(gameFileSystem, pcGameSdkFilePath); + return pcGameSdkFilePath; + } + + public static string GetScreenShotDirectory(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemScreenShotDirectories.TryGetValue(gameFileSystem, out string? screenShotDirectory)) + { + return screenShotDirectory; + } + + screenShotDirectory = string.Intern(Path.Combine(gameFileSystem.GetGameDirectory(), "ScreenShot")); + GameFileSystemScreenShotDirectories.Add(gameFileSystem, screenShotDirectory); + return screenShotDirectory; + } + + public static string GetDataDirectory(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemDataDirectories.TryGetValue(gameFileSystem, out string? dataDirectory)) + { + return dataDirectory; + } + + string dataDirectoryName = gameFileSystem.IsOversea() ? GameConstants.GenshinImpactData : GameConstants.YuanShenData; + dataDirectory = string.Intern(Path.Combine(gameFileSystem.GetGameDirectory(), dataDirectoryName)); + GameFileSystemDataDirectories.Add(gameFileSystem, dataDirectory); + return dataDirectory; + } + + public static string GetScriptVersionFilePath(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemScriptVersionFilePaths.TryGetValue(gameFileSystem, out string? scriptVersionFilePath)) + { + return scriptVersionFilePath; + } + + scriptVersionFilePath = string.Intern(Path.Combine(gameFileSystem.GetDataDirectory(), "Persistent", "ScriptVersion")); + GameFileSystemScriptVersionFilePaths.Add(gameFileSystem, scriptVersionFilePath); + return scriptVersionFilePath; + } + + public static string GetChunksDirectory(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemChunksDirectories.TryGetValue(gameFileSystem, out string? chunksDirectory)) + { + return chunksDirectory; + } + + chunksDirectory = string.Intern(Path.Combine(gameFileSystem.GetGameDirectory(), "chunks")); + GameFileSystemChunksDirectories.Add(gameFileSystem, chunksDirectory); + return chunksDirectory; + } + + public static string GetPredownloadStatusPath(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + if (GameFileSystemPredownloadStatusPaths.TryGetValue(gameFileSystem, out string? predownloadStatusPath)) + { + return predownloadStatusPath; + } + + predownloadStatusPath = string.Intern(Path.Combine(gameFileSystem.GetChunksDirectory(), "snap_hutao_predownload_status.json")); + GameFileSystemPredownloadStatusPaths.Add(gameFileSystem, predownloadStatusPath); + return predownloadStatusPath; + } + + public static bool IsOversea(this IGameFileSystem gameFileSystem) + { + ObjectDisposedException.ThrowIf(gameFileSystem.IsDisposed, gameFileSystem); + + string gameFileName = gameFileSystem.GetGameFileName(); + return gameFileName.ToUpperInvariant() switch + { + GameConstants.GenshinImpactFileNameUpper => true, + GameConstants.YuanShenFileNameUpper => false, + _ => throw HutaoException.Throw($"Invalid game executable file name:{gameFileName}"), + }; + } + public static bool TryGetGameVersion(this IGameFileSystem gameFileSystem, [NotNullWhen(true)] out string? version) { version = default!; - if (File.Exists(gameFileSystem.GameConfigFilePath)) + if (File.Exists(gameFileSystem.GetGameConfigurationFilePath())) { - foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GetGameConfigurationFilePath()).AsSpan()) { if (element is IniParameter { Key: "game_version" } parameter) { @@ -28,9 +182,9 @@ public static bool TryGetGameVersion(this IGameFileSystem gameFileSystem, [NotNu return true; } - if (File.Exists(gameFileSystem.ScriptVersionFilePath)) + if (File.Exists(gameFileSystem.GetScriptVersionFilePath())) { - version = File.ReadAllText(gameFileSystem.ScriptVersionFilePath); + version = File.ReadAllText(gameFileSystem.GetScriptVersionFilePath()); return true; } @@ -51,16 +205,16 @@ public static void GenerateConfigurationFile(this IGameFileSystem gameFileSystem uapc={"hk4e_cn":{"uapc":""},"hyp":{"uapc":""}} """; - string? directory = Path.GetDirectoryName(gameFileSystem.GameConfigFilePath); + string? directory = Path.GetDirectoryName(gameFileSystem.GetGameConfigurationFilePath()); ArgumentNullException.ThrowIfNull(directory); Directory.CreateDirectory(directory); - File.WriteAllText(gameFileSystem.GameConfigFilePath, content); + File.WriteAllText(gameFileSystem.GetGameConfigurationFilePath(), content); } public static bool TryUpdateConfigurationFile(this IGameFileSystem gameFileSystem, string version) { bool updated = false; - ImmutableArray ini = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath); + ImmutableArray ini = IniSerializer.DeserializeFromFile(gameFileSystem.GetGameConfigurationFilePath()); foreach (ref readonly IniElement element in ini.AsSpan()) { if (element is not IniParameter { Key: "game_version" } parameter) @@ -71,18 +225,18 @@ public static bool TryUpdateConfigurationFile(this IGameFileSystem gameFileSyste updated = parameter.Set(version); } - IniSerializer.SerializeToFile(gameFileSystem.GameConfigFilePath, ini); + IniSerializer.SerializeToFile(gameFileSystem.GetGameConfigurationFilePath(), ini); return updated; } public static bool TryFixConfigurationFile(this IGameFileSystem gameFileSystem, LaunchScheme launchScheme) { - if (!File.Exists(gameFileSystem.ScriptVersionFilePath)) + if (!File.Exists(gameFileSystem.GetScriptVersionFilePath())) { return false; } - string version = File.ReadAllText(gameFileSystem.ScriptVersionFilePath); + string version = File.ReadAllText(gameFileSystem.GetScriptVersionFilePath()); GenerateConfigurationFile(gameFileSystem, version, launchScheme); return true; @@ -90,13 +244,13 @@ public static bool TryFixConfigurationFile(this IGameFileSystem gameFileSystem, public static bool TryFixScriptVersion(this IGameFileSystem gameFileSystem) { - if (!File.Exists(gameFileSystem.GameConfigFilePath)) + if (!File.Exists(gameFileSystem.GetGameConfigurationFilePath())) { return false; } string? version = default; - foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).AsSpan()) + foreach (ref readonly IniElement element in IniSerializer.DeserializeFromFile(gameFileSystem.GetGameConfigurationFilePath()).AsSpan()) { if (element is IniParameter { Key: "game_version" } parameter) { @@ -105,11 +259,11 @@ public static bool TryFixScriptVersion(this IGameFileSystem gameFileSystem) } } - string? directory = Path.GetDirectoryName(gameFileSystem.ScriptVersionFilePath); + string? directory = Path.GetDirectoryName(gameFileSystem.GetScriptVersionFilePath()); ArgumentNullException.ThrowIfNull(directory); Directory.CreateDirectory(directory); - File.WriteAllText(gameFileSystem.ScriptVersionFilePath, version); + File.WriteAllText(gameFileSystem.GetScriptVersionFilePath(), version); return true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs index 6405be2b07..77e0ee94a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/IGameFileSystem.cs @@ -7,24 +7,7 @@ internal interface IGameFileSystem : IDisposable { string GameFilePath { get; } - string GameFileName { get; } - - string GameDirectory { get; } - - string GameConfigFilePath { get; } - - // ReSharper disable once InconsistentNaming - string PCGameSDKFilePath { get; } - - string ScreenShotDirectory { get; } - - string DataDirectory { get; } - - string ScriptVersionFilePath { get; } - - string ChunksDirectory { get; } - - string PredownloadStatusPath { get; } - GameAudioSystem Audio { get; } + + bool IsDisposed { get; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs index 94b4623450..739ac858ca 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs @@ -49,8 +49,8 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx return; } - // Backup config file, recover when a incompatible launcher deleted it. - context.ServiceProvider.GetRequiredService().Backup(gameFileSystem.GameConfigFilePath); + // Backup config file, recover when an incompatible launcher deleted it. + context.ServiceProvider.GetRequiredService().Backup(gameFileSystem.GetGameConfigurationFilePath()); await context.TaskContext.SwitchToMainThreadAsync(); context.UpdateGamePathEntry(); @@ -69,7 +69,7 @@ private static bool ShouldConvert(LaunchExecutionContext context, IGameFileSyste } // Executable name not match - if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GameFileName)) + if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GetGameFileName())) { return true; } @@ -77,7 +77,7 @@ private static bool ShouldConvert(LaunchExecutionContext context, IGameFileSyste if (!context.TargetScheme.IsOversea) { // [It's Bilibili channel xor PCGameSDK.dll exists] means we need to convert - if (context.TargetScheme.Channel is ChannelType.Bili ^ File.Exists(gameFileSystem.PCGameSDKFilePath)) + if (context.TargetScheme.Channel is ChannelType.Bili ^ File.Exists(gameFileSystem.GetPcGameSdkFilePath())) { return true; } @@ -88,7 +88,7 @@ private static bool ShouldConvert(LaunchExecutionContext context, IGameFileSyste private static async ValueTask EnsureGameResourceAsync(LaunchExecutionContext context, IGameFileSystem gameFileSystem, IProgress progress) { - string gameFolder = gameFileSystem.GameDirectory; + string gameFolder = gameFileSystem.GetGameDirectory(); context.Logger.LogInformation("Game folder: {GameFolder}", gameFolder); if (!CheckDirectoryPermissions(gameFolder)) @@ -103,7 +103,7 @@ private static async ValueTask EnsureGameResourceAsync(LaunchExecutionCont HoyoPlayClient hoyoPlayClient = context.ServiceProvider.GetRequiredService(); Response sdkResponse = await hoyoPlayClient.GetChannelSDKAsync(context.TargetScheme).ConfigureAwait(false); - if (!ResponseValidator.TryValidateWithoutUINotification(sdkResponse, out GameChannelSDKsWrapper? channelSDKs)) + if (!ResponseValidator.TryValidateWithoutUINotification(sdkResponse, out GameChannelSDKsWrapper? channelSdks)) { context.Result.Kind = LaunchExecutionResultKind.GameResourceIndexQueryInvalidResponse; context.Result.ErrorMessage = SH.FormatServiceGameLaunchExecutionGameResourceQueryIndexFailed(sdkResponse); @@ -128,7 +128,7 @@ private static async ValueTask EnsureGameResourceAsync(LaunchExecutionCont context.CurrentScheme, context.TargetScheme, gameFileSystem, - channelSDKs.GameChannelSDKs.SingleOrDefault(), + channelSdks.GameChannelSDKs.SingleOrDefault(), deprecatedFileConfigs.DeprecatedFileConfigurations.SingleOrDefault(), progress); @@ -174,7 +174,7 @@ private static async ValueTask EnsureGameResourceAsync(LaunchExecutionCont IPackageConverter packageConverter = context.ServiceProvider.GetRequiredKeyedService(type); - if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GameFileName)) + if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GetGameFileName())) { if (!await packageConverter.EnsureGameResourceAsync(packageConverterContext).ConfigureAwait(false)) { @@ -228,4 +228,4 @@ private static bool CheckDirectoryPermissions(string folder) return false; } } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs index 2a4cd88f5f..0980144112 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionGameProcessInitializationHandler.cs @@ -52,7 +52,7 @@ private static System.Diagnostics.Process InitializeGameProcess(LaunchExecutionC FileName = gameFileSystem.GameFilePath, UseShellExecute = true, Verb = "runas", - WorkingDirectory = gameFileSystem.GameDirectory, + WorkingDirectory = gameFileSystem.GetGameDirectory(), }, }; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs index 21cd8a9b57..fd9350fede 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionSetChannelOptionsHandler.cs @@ -18,7 +18,7 @@ public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchEx return; } - string configPath = gameFileSystem.GameConfigFilePath; + string configPath = gameFileSystem.GetGameConfigurationFilePath(); context.Logger.LogInformation("Game config file path: {ConfigPath}", configPath); ImmutableArray elements; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameAssetOperation.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameAssetOperation.cs index 7109863223..ba286702e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameAssetOperation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GameAssetOperation.cs @@ -38,7 +38,7 @@ public async ValueTask VerifyGamePackageIntegrityAsync if (context.Operation.GameChannelSDK is not null) { - string sdkPackageVersionFilePath = Path.Combine(context.Operation.GameFileSystem.GameDirectory, context.Operation.GameChannelSDK.PackageVersionFileName); + string sdkPackageVersionFilePath = Path.Combine(context.Operation.GameFileSystem.GetGameDirectory(), context.Operation.GameChannelSDK.PackageVersionFileName); if (!File.Exists(sdkPackageVersionFilePath)) { channelSdkConflicted = true; @@ -54,7 +54,7 @@ public async ValueTask VerifyGamePackageIntegrityAsync VersionItem? item = JsonSerializer.Deserialize(row, jsonOptions); ArgumentNullException.ThrowIfNull(item); - string path = Path.Combine(context.Operation.GameFileSystem.GameDirectory, item.RelativePath); + string path = Path.Combine(context.Operation.GameFileSystem.GetGameDirectory(), item.RelativePath); if (!item.Md5.Equals(await Hash.FileToHexStringAsync(HashAlgorithmName.MD5, path, token).ConfigureAwait(false), StringComparison.OrdinalIgnoreCase)) { channelSdkConflicted = true; @@ -102,7 +102,7 @@ public async ValueTask EnsureChannelSdkAsync(GamePackageServiceContext context) using (Stream sdkStream = await context.HttpClient.GetStreamAsync(context.Operation.GameChannelSDK.ChannelSdkPackage.Url, token).ConfigureAwait(false)) { - ZipFile.ExtractToDirectory(sdkStream, context.Operation.GameFileSystem.GameDirectory, true); + ZipFile.ExtractToDirectory(sdkStream, context.Operation.GameFileSystem.GetGameDirectory(), true); } } @@ -110,7 +110,7 @@ protected static async ValueTask VerifyAssetAsync(GamePackageServiceContext cont { CancellationToken token = context.CancellationToken; - string assetPath = Path.Combine(context.Operation.GameFileSystem.GameDirectory, asset.AssetProperty.AssetName); + string assetPath = Path.Combine(context.Operation.GameFileSystem.GetGameDirectory(), asset.AssetProperty.AssetName); if (asset.AssetProperty.AssetType is 64) { @@ -237,7 +237,7 @@ protected async ValueTask EnsureAssetAsync(GamePackageServiceContext context, So { if (asset.NewAsset.AssetType is 64) { - Directory.CreateDirectory(Path.Combine(context.Operation.GameFileSystem.GameDirectory, asset.NewAsset.AssetName)); + Directory.CreateDirectory(Path.Combine(context.Operation.GameFileSystem.GetGameDirectory(), asset.NewAsset.AssetName)); return; } @@ -270,7 +270,7 @@ private async ValueTask MergeDiffAssetAsync(GamePackageServiceContext context, S using (MemoryStream newAssetStream = memoryStreamFactory.GetStream()) { - string oldAssetPath = Path.Combine(context.Operation.GameFileSystem.GameDirectory, asset.OldAsset.AssetName); + string oldAssetPath = Path.Combine(context.Operation.GameFileSystem.GetGameDirectory(), asset.OldAsset.AssetName); if (!File.Exists(oldAssetPath)) { // File not found, skip this asset and repair later diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs index 6d5e10838c..1321b62889 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageOperationContext.cs @@ -29,15 +29,15 @@ public GamePackageOperationContext( string? extractDirectory) { Kind = kind; - Asset = serviceProvider.GetRequiredService>().Create(gameFileSystem.GameDirectory); + Asset = serviceProvider.GetRequiredService>().Create(gameFileSystem.GetGameDirectory()); GameFileSystem = gameFileSystem; LocalBranch = localBranch; RemoteBranch = remoteBranch; GameChannelSDK = gameChannelSDK; - ExtractOrGameDirectory = extractDirectory ?? gameFileSystem.GameDirectory; + ExtractOrGameDirectory = extractDirectory ?? gameFileSystem.GetGameDirectory(); ProxiedChunksDirectory = kind is GamePackageOperationKind.Verify - ? Path.Combine(gameFileSystem.ChunksDirectory, "repair") - : gameFileSystem.ChunksDirectory; + ? Path.Combine(gameFileSystem.GetChunksDirectory(), "repair") + : gameFileSystem.GetChunksDirectory(); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs index 0ac2c8b7a9..9b2092d912 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/GamePackageService.cs @@ -378,13 +378,13 @@ await DecodeManifestsAsync(context, context.Operation.RemoteBranch).ConfigureAwa context.Progress.Report(new GamePackageOperationReport.Reset(SH.ServiceGamePackageAdvancedPredownloading, totalBlocks, 0, totalBytes)); - if (!Directory.Exists(context.Operation.GameFileSystem.ChunksDirectory)) + if (!Directory.Exists(context.Operation.GameFileSystem.GetChunksDirectory())) { - Directory.CreateDirectory(context.Operation.GameFileSystem.ChunksDirectory); + Directory.CreateDirectory(context.Operation.GameFileSystem.GetChunksDirectory()); } PredownloadStatus predownloadStatus = new(context.Operation.RemoteBranch.Tag, false, uniqueTotalBlocks); - using (FileStream predownloadStatusStream = File.Create(context.Operation.GameFileSystem.PredownloadStatusPath)) + using (FileStream predownloadStatusStream = File.Create(context.Operation.GameFileSystem.GetPredownloadStatusPath())) { await JsonSerializer.SerializeAsync(predownloadStatusStream, predownloadStatus, jsonOptions).ConfigureAwait(false); } @@ -393,7 +393,7 @@ await DecodeManifestsAsync(context, context.Operation.RemoteBranch).ConfigureAwa context.Progress.Report(new GamePackageOperationReport.Finish(context.Operation.Kind)); - using (FileStream predownloadStatusStream = File.Create(context.Operation.GameFileSystem.PredownloadStatusPath)) + using (FileStream predownloadStatusStream = File.Create(context.Operation.GameFileSystem.GetPredownloadStatusPath())) { predownloadStatus.Finished = true; await JsonSerializer.SerializeAsync(predownloadStatusStream, predownloadStatus, jsonOptions).ConfigureAwait(false); @@ -409,7 +409,7 @@ await DecodeManifestsAsync(context, context.Operation.RemoteBranch).ConfigureAwa { ISophonClient client = scope.ServiceProvider .GetRequiredService>() - .Create(LaunchScheme.ExecutableIsOversea(context.Operation.GameFileSystem.GameFileName)); + .Create(context.Operation.GameFileSystem.IsOversea()); Response response = await client.GetBuildAsync(branch, token).ConfigureAwait(false); if (!ResponseValidator.TryValidate(response, serviceProvider, out build)) @@ -499,7 +499,7 @@ await DecodeManifestsAsync(context, context.Operation.RemoteBranch).ConfigureAwa .Where(ao => ao.Kind is SophonAssetOperationKind.Modify) .Select(ao => Path.GetFileName(ao.OldAsset.AssetName)) .ToList(); - string oldBlksDirectory = Path.Combine(context.Operation.GameFileSystem.DataDirectory, @"StreamingAssets\AssetBundles\blocks"); + string oldBlksDirectory = Path.Combine(context.Operation.GameFileSystem.GetDataDirectory(), @"StreamingAssets\AssetBundles\blocks"); foreach (string file in Directory.GetFiles(oldBlksDirectory, "*.blk", SearchOption.AllDirectories)) { string fileName = Path.GetFileName(file); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs index 3b9a60dd18..4efb125d0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs @@ -11,6 +11,7 @@ internal sealed partial class PackageOperationGameFileSystem : IGameFileSystem public PackageOperationGameFileSystem(string gameFilePath) { GameFilePath = gameFilePath; + Audio = new(GameFilePath); } public PackageOperationGameFileSystem(string gameFilePath, GameAudioSystem gameAudioSystem) @@ -21,46 +22,12 @@ public PackageOperationGameFileSystem(string gameFilePath, GameAudioSystem gameA public string GameFilePath { get; } - [field: MaybeNull] - public string GameFileName { get => field ??= Path.GetFileName(GameFilePath); } + public GameAudioSystem Audio { get; } - [field: MaybeNull] - public string GameDirectory - { - get - { - if (field is not null) - { - return field; - } - - string? directoryName = Path.GetDirectoryName(GameFilePath); - ArgumentException.ThrowIfNullOrEmpty(directoryName); - return field = directoryName; - } - } - - [field: MaybeNull] - public string GameConfigFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); } - - // ReSharper disable once InconsistentNaming - [field: MaybeNull] - public string PCGameSDKFilePath { get => field ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } - - public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); } - - public string DataDirectory { get => Path.Combine(GameDirectory, LaunchScheme.ExecutableIsOversea(GameFileName) ? GameConstants.GenshinImpactData : GameConstants.YuanShenData); } - - public string ScriptVersionFilePath { get => Path.Combine(DataDirectory, "Persistent", "ScriptVersion"); } - - public string ChunksDirectory { get => Path.Combine(GameDirectory, "chunks"); } - - public string PredownloadStatusPath { get => Path.Combine(ChunksDirectory, "snap_hutao_predownload_status.json"); } - - [field: MaybeNull] - public GameAudioSystem Audio { get => field ??= new(GameFilePath); } + public bool IsDisposed { get; private set; } public void Dispose() { + IsDisposed = true; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs index cb1eca9257..2f4554317c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/PackageConverterContext.cs @@ -74,8 +74,8 @@ private PackageConverterContext(CommonReferences common) ? (YuanShenData, GenshinImpactData) : (GenshinImpactData, YuanShenData); - FromDataFolder = Path.Combine(common.GameFileSystem.GameDirectory, FromDataFolderName); - ToDataFolder = Path.Combine(common.GameFileSystem.GameDirectory, ToDataFolderName); + FromDataFolder = Path.Combine(common.GameFileSystem.GetGameDirectory(), FromDataFolderName); + ToDataFolder = Path.Combine(common.GameFileSystem.GetGameDirectory(), ToDataFolderName); } public HttpClient HttpClient { get => Common.HttpClient; } @@ -109,7 +109,7 @@ public readonly string GetServerCacheTargetFilePath(string filePath) public readonly string GetGameFolderFilePath(string filePath) { - return Path.Combine(Common.GameFileSystem.GameDirectory, filePath); + return Path.Combine(Common.GameFileSystem.GetGameDirectory(), filePath); } [SuppressMessage("", "SH003")] diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ScatteredFilesPackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ScatteredFilesPackageConverter.cs index 3ecd521c6e..4ccddcb559 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ScatteredFilesPackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/ScatteredFilesPackageConverter.cs @@ -54,7 +54,7 @@ public async ValueTask EnsureGameResourceAsync(PackageConverterContext con // Step 1 context.Progress.Report(new(SH.ServiceGamePackageRequestPackageVerion)); RelativePathVersionItemDictionary remoteItems = await GetRemoteItemsAsync(context).ConfigureAwait(false); - RelativePathVersionItemDictionary localItems = await GetLocalItemsAsync(context.GameFileSystem.GameDirectory).ConfigureAwait(false); + RelativePathVersionItemDictionary localItems = await GetLocalItemsAsync(context.GameFileSystem.GetGameDirectory()).ConfigureAwait(false); // Step 2 List diffOperations = GetItemOperationInfos(remoteItems, localItems).ToList(); @@ -70,19 +70,20 @@ public async ValueTask EnsureGameResourceAsync(PackageConverterContext con public async ValueTask EnsureDeprecatedFilesAndSdkAsync(PackageConverterContext context) { // Just try to delete these files, always download from server when needed - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\PCGameSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\PCGameSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\EOSSDK-Win64-Shipping.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\EOSSDK-Win64-Shipping.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\PluginEOSSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\PluginEOSSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, "sdk_pkg_version")); + string gameDirectory = context.GameFileSystem.GetGameDirectory(); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\PCGameSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\PCGameSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\EOSSDK-Win64-Shipping.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\EOSSDK-Win64-Shipping.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\PluginEOSSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\PluginEOSSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, "sdk_pkg_version")); if (context.GameChannelSDK is not null) { using (Stream sdkWebStream = await context.HttpClient.GetStreamAsync(context.GameChannelSDK.ChannelSdkPackage.Url).ConfigureAwait(false)) { - ZipFile.ExtractToDirectory(sdkWebStream, context.GameFileSystem.GameDirectory, true); + ZipFile.ExtractToDirectory(sdkWebStream, gameDirectory, true); } } @@ -90,7 +91,7 @@ public async ValueTask EnsureDeprecatedFilesAndSdkAsync(PackageConverterContext { foreach (DeprecatedFile file in context.DeprecatedFiles.DeprecatedFiles) { - string filePath = Path.Combine(context.GameFileSystem.GameDirectory, file.Name); + string filePath = Path.Combine(gameDirectory, file.Name); FileOperation.Move(filePath, $"{filePath}.backup", true); } } @@ -196,7 +197,7 @@ private static async ValueTask SkipOrDownloadAsync(PackageConverterContext conte private static async ValueTask ReplacePackageVersionFilesAsync(PackageConverterContext context) { - foreach (string versionFilePath in Directory.EnumerateFiles(context.GameFileSystem.GameDirectory, "*pkg_version")) + foreach (string versionFilePath in Directory.EnumerateFiles(context.GameFileSystem.GetGameDirectory(), "*pkg_version")) { string versionFileName = Path.GetFileName(versionFilePath); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/SophonChunksPackageConverter.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/SophonChunksPackageConverter.cs index 7250b1912f..c67c12f400 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/SophonChunksPackageConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/SophonChunksPackageConverter.cs @@ -78,19 +78,20 @@ public async ValueTask EnsureGameResourceAsync(PackageConverterContext con public async ValueTask EnsureDeprecatedFilesAndSdkAsync(PackageConverterContext context) { // Just try to delete these files, always download from server when needed - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\PCGameSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\PCGameSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\EOSSDK-Win64-Shipping.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\EOSSDK-Win64-Shipping.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, YuanShenData, "Plugins\\PluginEOSSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, GenshinImpactData, "Plugins\\PluginEOSSDK.dll")); - FileOperation.Delete(Path.Combine(context.GameFileSystem.GameDirectory, "sdk_pkg_version")); + string gameDirectory = context.GameFileSystem.GetGameDirectory(); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\PCGameSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\PCGameSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\EOSSDK-Win64-Shipping.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\EOSSDK-Win64-Shipping.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, YuanShenData, "Plugins\\PluginEOSSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, GenshinImpactData, "Plugins\\PluginEOSSDK.dll")); + FileOperation.Delete(Path.Combine(gameDirectory, "sdk_pkg_version")); if (context.GameChannelSDK is not null) { using (Stream sdkWebStream = await context.HttpClient.GetStreamAsync(context.GameChannelSDK.ChannelSdkPackage.Url).ConfigureAwait(false)) { - ZipFile.ExtractToDirectory(sdkWebStream, context.GameFileSystem.GameDirectory, true); + ZipFile.ExtractToDirectory(sdkWebStream, gameDirectory, true); } } @@ -98,7 +99,7 @@ public async ValueTask EnsureDeprecatedFilesAndSdkAsync(PackageConverterContext { foreach (DeprecatedFile file in context.DeprecatedFiles.DeprecatedFiles) { - string filePath = Path.Combine(context.GameFileSystem.GameDirectory, file.Name); + string filePath = Path.Combine(gameDirectory, file.Name); FileOperation.Move(filePath, $"{filePath}.backup", true); } } @@ -378,7 +379,7 @@ private async ValueTask MergeDiffAssetAsync(PackageConverterContext context, Pac { using (MemoryStream newAssetStream = memoryStreamFactory.GetStream()) { - string oldAssetPath = Path.Combine(context.GameFileSystem.GameDirectory, asset.OldAsset.AssetName); + string oldAssetPath = Path.Combine(context.GameFileSystem.GetGameDirectory(), asset.OldAsset.AssetName); if (!File.Exists(oldAssetPath)) { // File not found, skip this asset and repair later diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs index 3f6881bf5e..f90e251381 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs @@ -35,6 +35,7 @@ public string DisplayName public bool IsNotCompatOnly { get; private protected set; } = true; + [Obsolete("Use IGameFileSystem.IsOversea instead")] public static bool ExecutableIsOversea(string gameFileName) { return gameFileName.ToUpperInvariant() switch diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs index 1cd4ca5261..a65944aa22 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GamePackageViewModel.cs @@ -107,14 +107,14 @@ public bool IsPredownloadFinished using (gameFileSystem) { - if (!File.Exists(gameFileSystem.PredownloadStatusPath)) + if (!File.Exists(gameFileSystem.GetPredownloadStatusPath())) { return false; } - if (JsonSerializer.Deserialize(File.ReadAllText(gameFileSystem.PredownloadStatusPath)) is { } predownloadStatus) + if (JsonSerializer.Deserialize(File.ReadAllText(gameFileSystem.GetPredownloadStatusPath())) is { } predownloadStatus) { - int fileCount = Directory.GetFiles(gameFileSystem.ChunksDirectory).Length - 1; + int fileCount = Directory.GetFiles(gameFileSystem.GetChunksDirectory()).Length - 1; return predownloadStatus.Finished && fileCount == predownloadStatus.TotalBlocks; } } @@ -173,9 +173,9 @@ protected override async ValueTask LoadOverrideAsync() LocalVersion = new(localVersion); } - if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.PredownloadStatusPath)) + if (!IsUpdateAvailable && PreVersion is null && File.Exists(gameFileSystem.GetPredownloadStatusPath())) { - File.Delete(gameFileSystem.PredownloadStatusPath); + File.Delete(gameFileSystem.GetPredownloadStatusPath()); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs index e3f8425a9b..cb7fedb162 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs @@ -79,13 +79,14 @@ private async Task HandleConfigurationFileNotFoundAsync() using (gameFileSystem) { - bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName); - LaunchGameConfigurationFixDialog dialog = await contentDialogFactory .CreateInstanceAsync() .ConfigureAwait(false); + bool isOversea = gameFileSystem.IsOversea(); + await taskContext.SwitchToMainThreadAsync(); + dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea); dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly); (bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index a9ecabeecd..0a43874814 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -277,8 +277,8 @@ private async Task OpenScreenshotFolderAsync() using (gameFileSystem) { - Directory.CreateDirectory(gameFileSystem.ScreenShotDirectory); - await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory); + Directory.CreateDirectory(gameFileSystem.GetScreenShotDirectory()); + await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.GetScreenShotDirectory()); } } From 17bb3e8b9871cf8ea9cddb31124b006477d35991 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Tue, 26 Nov 2024 16:32:02 +0800 Subject: [PATCH 6/6] refactor 2 --- .../Core/ExceptionService/HutaoException.cs | 16 -------------- .../Service/Game/GameAudioSystem.cs | 13 +++++------- .../Snap.Hutao/Service/Game/GameFileSystem.cs | 13 +++++++++++- ...aunchExecutionEnsureGameResourceHandler.cs | 5 ++--- .../PackageOperationGameFileSystem.cs | 10 ++------- .../Game/PathAbstraction/GamePathEntry.cs | 2 +- .../Game/RestrictedGamePathAccessExtension.cs | 2 +- .../Service/Game/Scheme/LaunchScheme.cs | 21 ------------------- .../LaunchGameInstallGameDialog.xaml.cs | 2 +- .../ViewModel/Game/LaunchGameViewModel.cs | 6 ++++++ .../Snap.Hutao/ViewModel/TestViewModel.cs | 2 +- 11 files changed, 31 insertions(+), 61 deletions(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs index 74a50f54e2..0f42bc8db5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoException.cs @@ -88,20 +88,4 @@ public static OperationCanceledException OperationCanceled(string message, Excep { throw new OperationCanceledException(message, innerException); } - - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - public static ObjectDisposedException ObjectDisposed(string objectName) - { - throw new ObjectDisposedException(objectName); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ObjectDisposedIf(bool condition, string objectName) - { - if (condition) - { - throw new ObjectDisposedException(objectName); - } - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs index b76cce6cc8..77ed000eea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameAudioSystem.cs @@ -7,15 +7,12 @@ namespace Snap.Hutao.Service.Game; internal sealed class GameAudioSystem { - public GameAudioSystem(string gameFilePath) + public GameAudioSystem(string gameDirectory) { - string? directory = Path.GetDirectoryName(gameFilePath); - ArgumentException.ThrowIfNullOrEmpty(directory); - - Chinese = File.Exists(Path.Combine(directory, GameConstants.AudioChinesePkgVersion)); - English = File.Exists(Path.Combine(directory, GameConstants.AudioEnglishPkgVersion)); - Japanese = File.Exists(Path.Combine(directory, GameConstants.AudioJapanesePkgVersion)); - Korean = File.Exists(Path.Combine(directory, GameConstants.AudioKoreanPkgVersion)); + Chinese = File.Exists(Path.Combine(gameDirectory, GameConstants.AudioChinesePkgVersion)); + English = File.Exists(Path.Combine(gameDirectory, GameConstants.AudioEnglishPkgVersion)); + Japanese = File.Exists(Path.Combine(gameDirectory, GameConstants.AudioJapanesePkgVersion)); + Korean = File.Exists(Path.Combine(gameDirectory, GameConstants.AudioKoreanPkgVersion)); } public GameAudioSystem(bool chinese, bool english, bool japanese, bool korean) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs index e1fc7c4c2b..251e29a56a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Service.Game.Package.Advanced; using Snap.Hutao.Service.Game.Scheme; using System.IO; @@ -20,10 +21,20 @@ public GameFileSystem(string gameFilePath, AsyncReaderWriterLock.Releaser releas public string GameFilePath { get; } [field: MaybeNull] - public GameAudioSystem Audio { get => field ??= new(GameFilePath); } + public GameAudioSystem Audio { get => field ??= new(this.GetGameDirectory()); } public bool IsDisposed { get; private set; } + public static IGameFileSystem Create(string gameFilePath, AsyncReaderWriterLock.Releaser releaser) + { + return new GameFileSystem(gameFilePath, releaser); + } + + public static IGameFileSystem CreateForPackageOperation(string gameFilePath, GameAudioSystem? gameAudioSystem = default) + { + return new PackageOperationGameFileSystem(gameFilePath, gameAudioSystem); + } + public void Dispose() { releaser.Dispose(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs index 739ac858ca..6ea01343c4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Launching/Handler/LaunchExecutionEnsureGameResourceHandler.cs @@ -68,8 +68,7 @@ private static bool ShouldConvert(LaunchExecutionContext context, IGameFileSyste return true; } - // Executable name not match - if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GetGameFileName())) + if (context.TargetScheme.IsOversea ^ gameFileSystem.IsOversea()) { return true; } @@ -174,7 +173,7 @@ private static async ValueTask EnsureGameResourceAsync(LaunchExecutionCont IPackageConverter packageConverter = context.ServiceProvider.GetRequiredKeyedService(type); - if (!context.TargetScheme.ExecutableMatches(gameFileSystem.GetGameFileName())) + if (context.TargetScheme.IsOversea ^ gameFileSystem.IsOversea()) { if (!await packageConverter.EnsureGameResourceAsync(packageConverterContext).ConfigureAwait(false)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs index 4efb125d0d..8089a0ae34 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Package/Advanced/PackageOperationGameFileSystem.cs @@ -8,16 +8,10 @@ namespace Snap.Hutao.Service.Game.Package.Advanced; internal sealed partial class PackageOperationGameFileSystem : IGameFileSystem { - public PackageOperationGameFileSystem(string gameFilePath) + public PackageOperationGameFileSystem(string gameFilePath, GameAudioSystem? gameAudioSystem = default) { GameFilePath = gameFilePath; - Audio = new(GameFilePath); - } - - public PackageOperationGameFileSystem(string gameFilePath, GameAudioSystem gameAudioSystem) - { - GameFilePath = gameFilePath; - Audio = gameAudioSystem; + Audio = gameAudioSystem ?? new(this.GetGameDirectory()); } public string GameFilePath { get; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs index 536b6488c6..f73be7ef7d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/PathAbstraction/GamePathEntry.cs @@ -6,7 +6,7 @@ namespace Snap.Hutao.Service.Game.PathAbstraction; internal sealed class GamePathEntry { [JsonPropertyName("Path")] - public string Path { get; private set; } = default!; + public string Path { get; init; } = default!; public static GamePathEntry Create(string path) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs index e8fa78d1e6..2041616b2f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/RestrictedGamePathAccessExtension.cs @@ -25,7 +25,7 @@ public static bool TryGetGameFileSystem(this IRestrictedGamePathAccess access, [ return false; } - fileSystem = new GameFileSystem(gamePath, releaser); + fileSystem = GameFileSystem.Create(gamePath, releaser); return true; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs index f90e251381..f925dd2f0b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/Scheme/LaunchScheme.cs @@ -35,29 +35,8 @@ public string DisplayName public bool IsNotCompatOnly { get; private protected set; } = true; - [Obsolete("Use IGameFileSystem.IsOversea instead")] - public static bool ExecutableIsOversea(string gameFileName) - { - return gameFileName.ToUpperInvariant() switch - { - GameConstants.GenshinImpactFileNameUpper => true, - GameConstants.YuanShenFileNameUpper => false, - _ => throw Requires.Fail("Invalid game executable file name:{0}", gameFileName), - }; - } - public bool Equals(ChannelOptions other) { return Channel == other.Channel && SubChannel == other.SubChannel && IsOversea == other.IsOversea; } - - public bool ExecutableMatches(string gameFileName) - { - return (IsOversea, gameFileName) switch - { - (true, GameConstants.GenshinImpactFileName) => true, - (false, GameConstants.YuanShenFileName) => true, - _ => false, - }; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs index 8fc6c0b0fc..89a7ef5190 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/LaunchGameInstallGameDialog.xaml.cs @@ -59,7 +59,7 @@ public async ValueTask> GetGameInstallOpti GameAudioSystem gameAudioSystem = new(Chinese, English, Japanese, Korean); string gamePath = Path.Combine(GameDirectory, SelectedScheme.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName); - return new(true, new(new PackageOperationGameFileSystem(gamePath, gameAudioSystem), SelectedScheme)); + return new(true, new(GameFileSystem.CreateForPackageOperation(gamePath, gameAudioSystem), SelectedScheme)); } private static void OnGameDirectoryChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 0a43874814..8b157ee8b1 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -133,11 +133,17 @@ public GamePathEntry? SelectedGamePathEntry get; set { + if (value is not null && !launchOptions.GamePathEntries.Contains(value)) + { + HutaoException.InvalidOperation("Selected game path entry is not in the game path entries."); + } + if (!SetProperty(ref field, value)) { return; } + // We are selecting from existing entries, so we don't need to update GamePathEntries if (launchOptions.GamePathLock.TryWriterLock(out AsyncReaderWriterLock.Releaser releaser)) { using (releaser) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index 1f563306f8..dc9fb23bec 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -436,7 +436,7 @@ private async Task ExtractGameExeAsync() if (result is ContentDialogResult.Primary) { - IGameFileSystem gameFileSystem = new PackageOperationGameFileSystem(Path.Combine(extractDirectory, ExtractExeOptions.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName)); + IGameFileSystem gameFileSystem = GameFileSystem.CreateForPackageOperation(Path.Combine(extractDirectory, ExtractExeOptions.IsOversea ? GameConstants.GenshinImpactFileName : GameConstants.YuanShenFileName)); GamePackageOperationContext context = new( serviceProvider,