mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
add console window
This commit is contained in:
@@ -10,9 +10,11 @@ DwmSetWindowAttribute
|
||||
GetDeviceCaps
|
||||
|
||||
// KERNEL32
|
||||
AllocConsole
|
||||
CloseHandle
|
||||
CreateEventW
|
||||
CreateRemoteThread
|
||||
FreeConsole
|
||||
GetModuleHandleW
|
||||
GetProcAddress
|
||||
K32EnumProcessModules
|
||||
|
||||
@@ -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<ConsoleWindowLifeTime>();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// A logger that writes messages in the debug output window only when a debugger is attached.
|
||||
/// </summary>
|
||||
internal sealed class DebugLogger : ILogger
|
||||
{
|
||||
private readonly string name;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DebugLogger"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the logger.</param>
|
||||
public DebugLogger(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
where TState : notnull
|
||||
{
|
||||
return NullScope.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
// If the filter is null, everything is enabled
|
||||
return logLevel != LogLevel.None;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[SuppressMessage("", "SH002")]
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="ILoggerFactory"/> class.
|
||||
/// </summary>
|
||||
internal static class DebugLoggerFactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a debug logger named 'Debug' to the factory.
|
||||
/// </summary>
|
||||
/// <param name="builder">The extension method argument.</param>
|
||||
/// <returns>builder</returns>
|
||||
public static ILoggingBuilder AddUnconditionalDebug(this ILoggingBuilder builder)
|
||||
{
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DebugLoggerProvider>());
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The provider for the <see cref="DebugLogger"/>.
|
||||
/// </summary>
|
||||
[ProviderAlias("Debug")]
|
||||
internal sealed class DebugLoggerProvider : ILoggerProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new DebugLogger(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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<ConsoleWindowLifeTime>();
|
||||
|
||||
builder.AddSimpleConsole();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// An empty scope without any logic
|
||||
/// </summary>
|
||||
internal sealed class NullScope : IDisposable
|
||||
{
|
||||
private NullScope()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实例
|
||||
/// </summary>
|
||||
public static NullScope Instance { get; } = new NullScope();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -9,59 +9,26 @@ namespace Snap.Hutao.Core.Setting;
|
||||
[HighQuality]
|
||||
internal static class SettingKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体矩形
|
||||
/// </summary>
|
||||
public const string WindowRect = "WindowRect";
|
||||
|
||||
/// <summary>
|
||||
/// 导航侧栏是否展开
|
||||
/// </summary>
|
||||
public const string IsNavPaneOpen = "IsNavPaneOpen";
|
||||
|
||||
/// <summary>
|
||||
/// 启动次数
|
||||
/// </summary>
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹
|
||||
/// </summary>
|
||||
public const string DataFolderPath = "DataFolderPath";
|
||||
|
||||
/// <summary>
|
||||
/// 通行证用户名(邮箱)
|
||||
/// </summary>
|
||||
public const string PassportUserName = "PassportUserName";
|
||||
|
||||
/// <summary>
|
||||
/// 通行证密码
|
||||
/// </summary>
|
||||
public const string PassportPassword = "PassportPassword";
|
||||
|
||||
/// <summary>
|
||||
/// 消息是否显示
|
||||
/// </summary>
|
||||
public const string IsInfoBarToggleChecked = "IsInfoBarToggleChecked";
|
||||
|
||||
/// <summary>
|
||||
/// 1.7.0 版本指引状态
|
||||
/// </summary>
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
|
||||
/// <summary>
|
||||
/// 排除的系统公告
|
||||
/// </summary>
|
||||
public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds";
|
||||
|
||||
/// <summary>
|
||||
/// 禁用元数据更新检查
|
||||
/// </summary>
|
||||
public const string SuppressMetadataInitialization = "SuppressMetadataInitialization";
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖管理员权限执行命令
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
@@ -2528,6 +2528,12 @@
|
||||
<data name="ViewServiceHutaoUserLoginOrRegisterHint" xml:space="preserve">
|
||||
<value>立即登录或注册</value>
|
||||
</data>
|
||||
<data name="ViewSettingAllocConsoleDescription" xml:space="preserve">
|
||||
<value>控制胡桃启动时是否开启控制台,重启后生效</value>
|
||||
</data>
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>调试控制台</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>打开文件夹</value>
|
||||
</data>
|
||||
|
||||
@@ -297,6 +297,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
|
||||
@@ -402,6 +402,13 @@
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageSettingDangerousHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewSettingAllocConsoleDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewSettingAllocConsoleHeader}"
|
||||
HeaderIcon="{cw:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding IsAllocConsoleDebugModeEnabled, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<cwc:SettingsCard
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
|
||||
@@ -14,26 +15,16 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
{
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 是否初始化完成
|
||||
/// </summary>
|
||||
public bool IsInitialized { get => isInitialized; set => SetProperty(ref isInitialized, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SemaphoreSlim DisposeLock { get; set; } = new(1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsViewDisposed { get; set; }
|
||||
|
||||
protected TaskCompletionSource<bool> Initialization { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化UI
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
[Command("OpenUICommand")]
|
||||
protected virtual async Task OpenUIAsync()
|
||||
{
|
||||
@@ -42,20 +33,11 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化界面数据
|
||||
/// </summary>
|
||||
/// <returns>初始化是否成功</returns>
|
||||
protected virtual ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保证 using scope 内的代码运行完成
|
||||
/// 防止 视图资源被回收
|
||||
/// </summary>
|
||||
/// <returns>解除执行限制</returns>
|
||||
protected async ValueTask<IDisposable> EnterCriticalExecutionAsync()
|
||||
{
|
||||
ThrowIfViewDisposed();
|
||||
@@ -64,10 +46,28 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当页面被释放后抛出异常
|
||||
/// </summary>
|
||||
/// <exception cref="OperationCanceledException">操作被用户取消</exception>
|
||||
protected bool SetProperty<T>(ref T storage, T value, Action<T> changedCallback, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (SetProperty(ref storage, value, propertyName))
|
||||
{
|
||||
changedCallback(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool SetProperty<T>(ref T storage, T value, Func<T, ValueTask> changedAsyncCallback, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (SetProperty(ref storage, value, propertyName))
|
||||
{
|
||||
changedAsyncCallback(value).SafeForget();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ThrowIfViewDisposed()
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
|
||||
@@ -55,37 +55,38 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
private GameAccount? selectedGameAccount;
|
||||
private GameResource? gameResource;
|
||||
|
||||
/// <summary>
|
||||
/// 已知的服务器方案
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public List<LaunchScheme> KnownSchemes { get => KnownLaunchSchemes.Get(); }
|
||||
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
|
||||
|
||||
/// <summary>
|
||||
/// 当前选择的服务器方案
|
||||
/// </summary>
|
||||
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<GameResource> response = await resourceClient
|
||||
.GetResourceAsync(scheme)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
GameResource = response.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏账号集合
|
||||
/// </summary>
|
||||
public ObservableCollection<GameAccount>? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的账号
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// 游戏资源
|
||||
/// </summary>
|
||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||
|
||||
protected override async ValueTask<bool> 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<GameResource> response = await resourceClient
|
||||
.GetResourceAsync(scheme)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
GameResource = response.Data;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
|
||||
@@ -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<bool> InitializeUIAsync()
|
||||
{
|
||||
CacheFolderView = new(taskContext, runtimeOptions.LocalCache);
|
||||
|
||||
Reference in New Issue
Block a user