diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt index 80fd8e62..3a8308d4 100644 --- a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt +++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt @@ -10,9 +10,11 @@ DwmSetWindowAttribute GetDeviceCaps // KERNEL32 +AllocConsole CloseHandle CreateEventW CreateRemoteThread +FreeConsole GetModuleHandleW GetProcAddress K32EnumProcessModules diff --git a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs index 50c41966..1f515abd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/DependencyInjection/DependencyInjection.cs @@ -24,7 +24,7 @@ internal static class DependencyInjection ServiceProvider serviceProvider = new ServiceCollection() // Microsoft extension - .AddLogging(builder => builder.AddUnconditionalDebug()) + .AddLogging(builder => builder.AddConsoleWindow()) .AddMemoryCache() // Hutao extensions @@ -39,6 +39,7 @@ internal static class DependencyInjection Ioc.Default.ConfigureServices(serviceProvider); + serviceProvider.InitializeConsoleWindow(); serviceProvider.InitializeCulture(); return serviceProvider; @@ -61,4 +62,9 @@ internal static class DependencyInjection SH.Culture = cultureInfo; } + + private static void InitializeConsoleWindow(this IServiceProvider serviceProvider) + { + _ = serviceProvider.GetRequiredService(); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs new file mode 100644 index 00000000..66f4ceaf --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/ConsoleWindowLifeTime.cs @@ -0,0 +1,28 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Setting; +using static Windows.Win32.PInvoke; + +namespace Snap.Hutao.Core.Logging; + +internal sealed class ConsoleWindowLifeTime : IDisposable +{ + private readonly bool consoleWindowAllocated; + + public ConsoleWindowLifeTime() + { + if (LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false)) + { + consoleWindowAllocated = AllocConsole(); + } + } + + public void Dispose() + { + if (consoleWindowAllocated) + { + FreeConsole(); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs deleted file mode 100644 index b90e42ab..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLogger.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Diagnostics; - -namespace Snap.Hutao.Core.Logging; - -/// -/// A logger that writes messages in the debug output window only when a debugger is attached. -/// -internal sealed class DebugLogger : ILogger -{ - private readonly string name; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the logger. - public DebugLogger(string name) - { - this.name = name; - } - - /// - public IDisposable BeginScope(TState state) - where TState : notnull - { - return NullScope.Instance; - } - - /// - public bool IsEnabled(LogLevel logLevel) - { - // If the filter is null, everything is enabled - return logLevel != LogLevel.None; - } - - /// - [SuppressMessage("", "SH002")] - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - if (!IsEnabled(logLevel)) - { - return; - } - - ArgumentNullException.ThrowIfNull(formatter); - - string message = formatter(state, exception); - - if (string.IsNullOrEmpty(message)) - { - return; - } - - message = $"{logLevel}: {message}"; - - if (exception is not null) - { - message += Environment.NewLine + Environment.NewLine + exception; - } - - DebugWriteLine(message, name); - } - - private static void DebugWriteLine(string message, string name) - { - Debug.WriteLine(message, category: name); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs deleted file mode 100644 index 852a90c9..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerFactoryExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Snap.Hutao.Core.Logging; - -/// -/// Extension methods for the class. -/// -internal static class DebugLoggerFactoryExtensions -{ - /// - /// Adds a debug logger named 'Debug' to the factory. - /// - /// The extension method argument. - /// builder - public static ILoggingBuilder AddUnconditionalDebug(this ILoggingBuilder builder) - { - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); - - return builder; - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs deleted file mode 100644 index 2cf5dd5e..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DebugLoggerProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Core.Logging; - -/// -/// The provider for the . -/// -[ProviderAlias("Debug")] -internal sealed class DebugLoggerProvider : ILoggerProvider -{ - /// - public ILogger CreateLogger(string name) - { - return new DebugLogger(name); - } - - /// - public void Dispose() - { - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtensions.cs new file mode 100644 index 00000000..1dd99ff7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LoggerFactoryExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Logging; + +internal static class LoggerFactoryExtensions +{ + public static ILoggingBuilder AddConsoleWindow(this ILoggingBuilder builder) + { + builder.Services.AddSingleton(); + + builder.AddSimpleConsole(); + return builder; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs deleted file mode 100644 index 65db7224..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/NullScope.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Core.Logging; - -/// -/// An empty scope without any logic -/// -internal sealed class NullScope : IDisposable -{ - private NullScope() - { - } - - /// - /// 实例 - /// - public static NullScope Instance { get; } = new NullScope(); - - /// - public void Dispose() - { - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs index d67cc641..3565689c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs @@ -9,59 +9,26 @@ namespace Snap.Hutao.Core.Setting; [HighQuality] internal static class SettingKeys { - /// - /// 窗体矩形 - /// public const string WindowRect = "WindowRect"; - /// - /// 导航侧栏是否展开 - /// public const string IsNavPaneOpen = "IsNavPaneOpen"; - /// - /// 启动次数 - /// public const string LaunchTimes = "LaunchTimes"; - /// - /// 数据文件夹 - /// public const string DataFolderPath = "DataFolderPath"; - /// - /// 通行证用户名(邮箱) - /// public const string PassportUserName = "PassportUserName"; - /// - /// 通行证密码 - /// public const string PassportPassword = "PassportPassword"; - /// - /// 消息是否显示 - /// public const string IsInfoBarToggleChecked = "IsInfoBarToggleChecked"; - /// - /// 1.7.0 版本指引状态 - /// public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState"; - /// - /// 排除的系统公告 - /// public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds"; - /// - /// 禁用元数据更新检查 - /// public const string SuppressMetadataInitialization = "SuppressMetadataInitialization"; - /// - /// 覆盖管理员权限执行命令 - /// public const string OverrideElevationRequirement = "OverrideElevationRequirement"; public const string CultivationAvatarLevelCurrent = "CultivationAvatarLevelCurrent"; @@ -83,4 +50,6 @@ internal static class SettingKeys public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented"; public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever"; + + public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled"; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 2ba84fb3..5ee7f8a2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2528,6 +2528,12 @@ 立即登录或注册 + + 控制胡桃启动时是否开启控制台,重启后生效 + + + 调试控制台 + 打开文件夹 diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index a1ed520d..96133dfe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -297,6 +297,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 4a25e5aa..3a65b2f0 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -402,6 +402,13 @@ Foreground="{ThemeResource SystemFillColorCriticalBrush}" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingDangerousHeader}"/> + + + + - /// 是否初始化完成 - /// public bool IsInitialized { get => isInitialized; set => SetProperty(ref isInitialized, value); } - /// public CancellationToken CancellationToken { get; set; } - /// public SemaphoreSlim DisposeLock { get; set; } = new(1); - /// public bool IsViewDisposed { get; set; } protected TaskCompletionSource Initialization { get; } = new(); - /// - /// 异步初始化UI - /// - /// 任务 [Command("OpenUICommand")] protected virtual async Task OpenUIAsync() { @@ -42,20 +33,11 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel Initialization.TrySetResult(IsInitialized); } - /// - /// 异步初始化界面数据 - /// - /// 初始化是否成功 protected virtual ValueTask InitializeUIAsync() { return ValueTask.FromResult(true); } - /// - /// 保证 using scope 内的代码运行完成 - /// 防止 视图资源被回收 - /// - /// 解除执行限制 protected async ValueTask EnterCriticalExecutionAsync() { ThrowIfViewDisposed(); @@ -64,10 +46,28 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel return disposable; } - /// - /// 当页面被释放后抛出异常 - /// - /// 操作被用户取消 + protected bool SetProperty(ref T storage, T value, Action changedCallback, [CallerMemberName] string? propertyName = null) + { + if (SetProperty(ref storage, value, propertyName)) + { + changedCallback(value); + return true; + } + + return false; + } + + protected bool SetProperty(ref T storage, T value, Func changedAsyncCallback, [CallerMemberName] string? propertyName = null) + { + if (SetProperty(ref storage, value, propertyName)) + { + changedAsyncCallback(value).SafeForget(); + return true; + } + + return false; + } + private void ThrowIfViewDisposed() { if (IsViewDisposed) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index 437e333c..01bb8cdd 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -55,37 +55,38 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel private GameAccount? selectedGameAccount; private GameResource? gameResource; - /// - /// 已知的服务器方案 - /// - [SuppressMessage("", "CA1822")] - public List KnownSchemes { get => KnownLaunchSchemes.Get(); } + public List KnownSchemes { get; } = KnownLaunchSchemes.Get(); - /// - /// 当前选择的服务器方案 - /// public LaunchScheme? SelectedScheme { - get => selectedScheme; set + get => selectedScheme; + set { - if (SetProperty(ref selectedScheme, value)) + SetProperty(ref selectedScheme, value, UpdateGameResourceAsync); + + async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme) { - if (value is not null) + if (scheme is null) { - UpdateGameResourceAsync(value).SafeForget(); + return; + } + + await taskContext.SwitchToBackgroundAsync(); + Web.Response.Response response = await resourceClient + .GetResourceAsync(scheme) + .ConfigureAwait(false); + + if (response.IsOk()) + { + await taskContext.SwitchToMainThreadAsync(); + GameResource = response.Data; } } } } - /// - /// 游戏账号集合 - /// public ObservableCollection? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); } - /// - /// 选中的账号 - /// public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } public LaunchOptions LaunchOptions { get => launchOptions; } @@ -96,9 +97,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel public AppOptions AppOptions { get => appOptions; } - /// - /// 游戏资源 - /// public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); } protected override async ValueTask InitializeUIAsync() @@ -165,20 +163,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel return true; } - private async ValueTask UpdateGameResourceAsync(LaunchScheme scheme) - { - await taskContext.SwitchToBackgroundAsync(); - Web.Response.Response response = await resourceClient - .GetResourceAsync(scheme) - .ConfigureAwait(false); - - if (response.IsOk()) - { - await taskContext.SwitchToMainThreadAsync(); - GameResource = response.Data; - } - } - [Command("LaunchCommand")] private async Task LaunchAsync() { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs index cae48b9e..6b09771c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Setting/SettingViewModel.cs @@ -107,6 +107,13 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel public IPInformation? IPInformation { get => ipInformation; private set => SetProperty(ref ipInformation, value); } + [SuppressMessage("", "CA1822")] + public bool IsAllocConsoleDebugModeEnabled + { + get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false); + set => LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, value); + } + protected override async ValueTask InitializeUIAsync() { CacheFolderView = new(taskContext, runtimeOptions.LocalCache);