diff --git a/src/Snap.Hutao/Snap.Hutao.Test/PlatformExtensions/DependencyInjectionTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/PlatformExtensions/DependencyInjectionTest.cs index f0026e20..0075610c 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/PlatformExtensions/DependencyInjectionTest.cs +++ b/src/Snap.Hutao/Snap.Hutao.Test/PlatformExtensions/DependencyInjectionTest.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; namespace Snap.Hutao.Test.PlatformExtensions; @@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest .AddSingleton() .AddScoped() .AddTransient(typeof(IGenericService<>), typeof(GenericService<>)) + .AddLogging(builder => builder.AddConsole()) .BuildServiceProvider(); [TestMethod] @@ -41,6 +43,13 @@ public sealed class DependencyInjectionTest } } + [TestMethod] + public void LoggerWithInterfaceTypeCanBeResolved() + { + Assert.IsNotNull(services.GetService>()); + Assert.IsNotNull(services.GetRequiredService().CreateLogger(nameof(IScopedService))); + } + private interface IService { Guid Id { get; } diff --git a/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj b/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj index 6bc344e4..4ce6bd98 100644 --- a/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj +++ b/src/Snap.Hutao/Snap.Hutao.Test/Snap.Hutao.Test.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs index 7c4655ca..fe550299 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs @@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent return; } - if (serviceProvider.IsDisposedSlow()) + if (serviceProvider.IsDisposed()) { return; } @@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent return; } - if (serviceProvider.IsDisposedSlow()) + if (serviceProvider.IsDisposed()) { return; } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs index bdaafd82..12cc32ce 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/ServiceProviderExtension.cs @@ -18,13 +18,22 @@ internal static class ServiceProviderExtension } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDisposedSlow(this IServiceProvider? serviceProvider) + public static bool IsDisposed(this IServiceProvider? serviceProvider) { if (serviceProvider is null) { return true; } + if (serviceProvider is ServiceProvider serviceProviderImpl) + { + return GetPrivateDisposed(serviceProviderImpl); + } + return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true; } + + // private bool _disposed; + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")] + private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs index b55e7c58..609216d4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/HutaoExceptionKind.cs @@ -17,4 +17,5 @@ internal enum HutaoExceptionKind // Service GachaStatisticsInvalidItemId, GameFpsUnlockingFailed, + GameConfigInvalidChannelOptions, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/IdentifyMonitorWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/IdentifyMonitorWindow.xaml.cs index aa291a73..604697b2 100644 --- a/src/Snap.Hutao/Snap.Hutao/IdentifyMonitorWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/IdentifyMonitorWindow.xaml.cs @@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window } public string Monitor { get; private set; } + + public static async ValueTask IdentifyAllMonitorsAsync(int secondsDelay) + { + List windows = []; + + IReadOnlyList displayAreas = DisplayArea.FindAll(); + for (int i = 0; i < displayAreas.Count; i++) + { + windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1)); + } + + foreach (IdentifyMonitorWindow window in windows) + { + window.Activate(); + } + + await Delay.FromSeconds(secondsDelay).ConfigureAwait(true); + + foreach (IdentifyMonitorWindow window in windows) + { + window.Close(); + } + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs index 0f9acb04..6e7529eb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameFileSystem.cs @@ -36,4 +36,6 @@ internal sealed class GameFileSystem public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); } public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); } + + public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/IViewModelSupportLaunchExecution.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/IViewModelSupportLaunchExecution.cs index d01eae50..98fd6d0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/IViewModelSupportLaunchExecution.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/IViewModelSupportLaunchExecution.cs @@ -1,12 +1,21 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Model.Entity; using Snap.Hutao.Service.Game.PathAbstraction; +using Snap.Hutao.Service.Notification; using System.Collections.Immutable; namespace Snap.Hutao.ViewModel.Game; internal interface IViewModelSupportLaunchExecution { - void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList gamePathEntries, GamePathEntry? selectedEntry); + LaunchGameShared Shared { get; } + + GameAccount? SelectedGameAccount { get; } + + void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList gamePathEntries, GamePathEntry? selectedEntry) + { + // Do nothing + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs new file mode 100644 index 00000000..3bf0c471 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameLaunchExecution.cs @@ -0,0 +1,36 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Game.Launching; +using Snap.Hutao.Service.Game.Scheme; +using Snap.Hutao.Service.Notification; + +namespace Snap.Hutao.ViewModel.Game; + +internal static class LaunchGameLaunchExecution +{ + public static async ValueTask LaunchExecutionAsync(this IViewModelSupportLaunchExecution launchExecution) + { + IServiceProvider root = Ioc.Default; + IInfoBarService infoBarService = root.GetRequiredService(); + ILogger logger = root.GetRequiredService>(); + + LaunchScheme? scheme = launchExecution.Shared.GetCurrentLaunchSchemeFromConfigFile(); + try + { + // Root service provider is required. + LaunchExecutionContext context = new(root, launchExecution, scheme, launchExecution.SelectedGameAccount); + LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false); + + if (result.Kind is not LaunchExecutionResultKind.Ok) + { + infoBarService.Warning(result.ErrorMessage); + } + } + catch (Exception ex) + { + logger.LogCritical(ex, "Launch failed"); + infoBarService.Error(ex); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs index 16ca9bf8..567711ae 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameShared.cs @@ -41,7 +41,7 @@ internal sealed partial class LaunchGameShared if (!IgnoredInvalidChannelOptions.Contains(options)) { // 后台收集 - throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}"); + HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}"); } } @@ -70,7 +70,9 @@ internal sealed partial class LaunchGameShared string persistentScriptVersionFile = Path.Combine(gameFileSystem.GameDirectory, dataFolder, "Persistent", "ScriptVersion"); string version = await File.ReadAllTextAsync(persistentScriptVersionFile).ConfigureAwait(false); - 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); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index e3795711..0f449ef3 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -60,6 +60,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView private GamePathEntry? selectedGamePathEntry; private GameAccountFilter? gameAccountFilter; + LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; } + public LaunchOptions LaunchOptions { get => launchOptions; } public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } @@ -83,7 +85,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); } - [AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))] public bool GamePathSelectedAndValid { get => gamePathSelectedAndValid; @@ -149,7 +150,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView public ImmutableList GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); } - [AlsoSets(nameof(GamePathSelectedAndValid))] public GamePathEntry? SelectedGamePathEntry { get => selectedGamePathEntry; @@ -184,25 +184,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView [Command("IdentifyMonitorsCommand")] private static async Task IdentifyMonitorsAsync() { - List windows = []; - - IReadOnlyList displayAreas = DisplayArea.FindAll(); - for (int i = 0; i < displayAreas.Count; i++) - { - windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1)); - } - - foreach (IdentifyMonitorWindow window in windows) - { - window.Activate(); - } - - await Delay.FromSeconds(3).ConfigureAwait(true); - - foreach (IdentifyMonitorWindow window in windows) - { - window.Close(); - } + await IdentifyMonitorWindow.IdentifyAllMonitorsAsync(3); } [Command("SetGamePathCommand")] @@ -234,21 +216,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView [Command("LaunchCommand")] private async Task LaunchAsync() { - try - { - LaunchExecutionContext context = new(Ioc.Default, this, SelectedScheme, SelectedGameAccount); - LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false); - - if (result.Kind is not LaunchExecutionResultKind.Ok) - { - infoBarService.Warning(result.ErrorMessage); - } - } - catch (Exception ex) - { - logger.LogCritical(ex, "Launch failed"); - infoBarService.Error(ex); - } + await this.LaunchExecutionAsync().ConfigureAwait(false); } [Command("DetectGameAccountCommand")] @@ -269,7 +237,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView SelectedGameAccount = account; } } - catch (UserdataCorruptedException ex) + catch (Exception ex) { infoBarService.Error(ex); } @@ -278,47 +246,54 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView [Command("AttachGameAccountCommand")] private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount) { - if (gameAccount is not null) + if (gameAccount is null) { - if (userService.Current?.SelectedUserGameRole is { } role) - { - gameService.AttachGameAccountToUid(gameAccount, role.GameUid); - } - else - { - infoBarService.Warning(SH.MustSelectUserAndUid); - } + return; + } + + if (userService.Current?.SelectedUserGameRole is { } role) + { + gameService.AttachGameAccountToUid(gameAccount, role.GameUid); + } + else + { + infoBarService.Warning(SH.MustSelectUserAndUid); } } [Command("ModifyGameAccountCommand")] private async Task ModifyGameAccountAsync(GameAccount? gameAccount) { - if (gameAccount is not null) + if (gameAccount is null) { - await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false); + return; } + + await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false); } [Command("RemoveGameAccountCommand")] private async Task RemoveGameAccountAsync(GameAccount? gameAccount) { - if (gameAccount is not null) + if (gameAccount is null) { - await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false); + return; } + + await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false); } [Command("OpenScreenshotFolderCommand")] private async Task OpenScreenshotFolderAsync() { - string game = LaunchOptions.GamePath; - string? directory = Path.GetDirectoryName(game); - ArgumentException.ThrowIfNullOrEmpty(directory); - string screenshot = Path.Combine(directory, "ScreenShot"); - if (Directory.Exists(screenshot)) + if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem)) { - await Windows.System.Launcher.LaunchFolderPathAsync(screenshot); + return; + } + + if (Directory.Exists(gameFileSystem.ScreenShotDirectory)) + { + await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory); } } @@ -326,12 +301,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView { if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme))) { - UpdateGameResourceAsync(value).SafeForget(); - await UpdateGameAccountsViewAsync().ConfigureAwait(false); - // Clear the selected game account to prevent setting // incorrect CN/OS account when scheme not match SelectedGameAccount = default; + + await UpdateGameAccountsViewAsync().ConfigureAwait(false); + UpdateGameResourceAsync(value).SafeForget(); } async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index d37ddd7d..bd134f3c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -22,7 +22,6 @@ namespace Snap.Hutao.ViewModel.Game; internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim, IViewModelSupportLaunchExecution { private readonly LaunchStatusOptions launchStatusOptions; - private readonly ILogger logger; private readonly LaunchGameShared launchGameShared; private readonly IInfoBarService infoBarService; private readonly IGameServiceFacade gameService; @@ -32,6 +31,8 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli private GameAccount? selectedGameAccount; private GameAccountFilter? gameAccountFilter; + LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; } + public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } @@ -41,10 +42,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli /// public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } - public void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList gamePathEntries, GamePathEntry? selectedEntry) - { - } - /// protected override async Task OpenUIAsync() { @@ -59,7 +56,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme); } } - catch (UserdataCorruptedException ex) + catch (Exception ex) { infoBarService.Error(ex); } @@ -76,23 +73,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli [Command("LaunchCommand")] private async Task LaunchAsync() { - IInfoBarService infoBarService = ServiceProvider.GetRequiredService(); - LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(); - - try - { - LaunchExecutionContext context = new(Ioc.Default, this, scheme, SelectedGameAccount); - LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false); - - if (result.Kind is not LaunchExecutionResultKind.Ok) - { - infoBarService.Warning(result.ErrorMessage); - } - } - catch (Exception ex) - { - logger.LogCritical(ex, "Launch failed"); - infoBarService.Error(ex); - } + await this.LaunchExecutionAsync().ConfigureAwait(false); } } \ No newline at end of file