mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
merge Win32 call to main project
This commit is contained in:
BIN
res/HutaoIcon.psd
Normal file
BIN
res/HutaoIcon.psd
Normal file
Binary file not shown.
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.10-beta">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.Win32Metadata" Version="25.0.28-preview" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Foundation\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Windows.Win32.UI.WindowsAndMessaging;
|
|
||||||
public partial struct MINMAXINFO
|
|
||||||
{
|
|
||||||
public static unsafe ref MINMAXINFO FromPointer(nint value)
|
|
||||||
{
|
|
||||||
return ref *(MINMAXINFO*)value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\Se
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{29209B14-A6E1-442E-9287-2C65B03C96CD}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -84,22 +82,6 @@ Global
|
|||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.Build.0 = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.ActiveCfg = Release|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.Build.0 = Release|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.Build.0 = Release|x64
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Cancellable;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 表示支持取消加载的异步页面
|
|
||||||
/// 在被导航到其他页面前触发取消异步通知
|
|
||||||
/// </summary>
|
|
||||||
public class CancellablePage : Page
|
|
||||||
{
|
|
||||||
private readonly CancellationTokenSource viewLoadingConcellationTokenSource = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TViewModel">视图模型类型</typeparam>
|
|
||||||
public void InitializeWith<TViewModel>()
|
|
||||||
where TViewModel : class, ISupportCancellation
|
|
||||||
{
|
|
||||||
ISupportCancellation viewModel = Ioc.Default.GetRequiredService<TViewModel>();
|
|
||||||
viewModel.CancellationToken = viewLoadingConcellationTokenSource.Token;
|
|
||||||
DataContext = viewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnNavigatingFrom(e);
|
|
||||||
viewLoadingConcellationTokenSource.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Cancellable;
|
namespace Snap.Hutao.Control;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 指示此类支持取消任务
|
/// 指示此类支持取消任务
|
||||||
51
src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs
Normal file
51
src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Control;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 表示支持取消加载的异步页面
|
||||||
|
/// 在被导航到其他页面前触发取消异步通知
|
||||||
|
/// </summary>
|
||||||
|
public class ScopedPage : Page
|
||||||
|
{
|
||||||
|
private readonly CancellationTokenSource viewLoadingCancellationTokenSource = new();
|
||||||
|
private readonly IServiceScope serviceScope;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的页面
|
||||||
|
/// </summary>
|
||||||
|
public ScopedPage()
|
||||||
|
{
|
||||||
|
serviceScope = Ioc.Default.CreateScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IServiceScope.ServiceProvider"/>
|
||||||
|
public IServiceProvider ServiceProvider { get => serviceScope.ServiceProvider; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TViewModel">视图模型类型</typeparam>
|
||||||
|
public void InitializeWith<TViewModel>()
|
||||||
|
where TViewModel : class, ISupportCancellation
|
||||||
|
{
|
||||||
|
ISupportCancellation viewModel = ServiceProvider.GetRequiredService<TViewModel>();
|
||||||
|
viewModel.CancellationToken = viewLoadingCancellationTokenSource.Token;
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnNavigatingFrom(e);
|
||||||
|
viewLoadingCancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
// Try dispose scope when page is not presented
|
||||||
|
serviceScope.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,45 @@ public static class Must
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws an <see cref="ArgumentOutOfRangeException"/> if a condition does not evaluate to true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition">The condition to check.</param>
|
||||||
|
/// <param name="message">message</param>
|
||||||
|
/// <param name="parameterName">The name of the parameter to blame in the exception, if thrown.</param>
|
||||||
|
public static void Range([DoesNotReturnIf(false)] bool condition, string? message, [CallerArgumentExpression("condition")] string? parameterName = null)
|
||||||
|
{
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(message, parameterName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">异常消息</param>
|
||||||
|
/// <returns>异常的任务</returns>
|
||||||
|
[SuppressMessage("", "VSTHRD200")]
|
||||||
|
public static Task Fault(string message)
|
||||||
|
{
|
||||||
|
InvalidOperationException exception = new(message);
|
||||||
|
return Task.FromException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务异常
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">任务结果类型</typeparam>
|
||||||
|
/// <param name="message">异常消息</param>
|
||||||
|
/// <returns>异常的任务</returns>
|
||||||
|
[SuppressMessage("", "VSTHRD200")]
|
||||||
|
public static Task<T> Fault<T>(string message)
|
||||||
|
{
|
||||||
|
InvalidOperationException exception = new(message);
|
||||||
|
return Task.FromException<T>(exception);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class SystemBackdrop
|
|||||||
backdropController = new();
|
backdropController = new();
|
||||||
|
|
||||||
// Mica Alt
|
// Mica Alt
|
||||||
// backdropController.Kind = MicaKind.BaseAlt;
|
backdropController.Kind = MicaKind.BaseAlt;
|
||||||
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
|
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
|
||||||
backdropController.SetSystemBackdropConfiguration(configuration);
|
backdropController.SetSystemBackdropConfiguration(configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -78,24 +78,18 @@ internal class WindowSubclassManager : IDisposable
|
|||||||
dragBarProc = null;
|
dragBarProc = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
|
private unsafe LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
|
||||||
{
|
{
|
||||||
switch (uMsg)
|
switch (uMsg)
|
||||||
{
|
{
|
||||||
case WM_GETMINMAXINFO:
|
case WM_GETMINMAXINFO:
|
||||||
{
|
{
|
||||||
double scalingFactor = Persistence.GetScaleForWindow(hwnd);
|
double scalingFactor = Persistence.GetScaleForWindow(hwnd);
|
||||||
ref MINMAXINFO info = ref MINMAXINFO.FromPointer(lParam);
|
MINMAXINFO* info = (MINMAXINFO*)lParam.Value;
|
||||||
info.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.x);
|
info->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info->ptMinTrackSize.X);
|
||||||
info.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.y);
|
info->ptMinTrackSize.X = (int)Math.Max(MinHeight * scalingFactor, info->ptMinTrackSize.X);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WM_NCRBUTTONDOWN:
|
|
||||||
case WM_NCRBUTTONUP:
|
|
||||||
{
|
|
||||||
return new(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:view="using:Snap.Hutao.View"
|
xmlns:view="using:Snap.Hutao.View"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d">
|
||||||
Closed="MainWindowClosed">
|
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<view:TitleView
|
<view:TitleView
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Context.Database;
|
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.Core.Windowing;
|
||||||
using Snap.Hutao.Message;
|
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
|
|
||||||
@@ -13,32 +10,22 @@ namespace Snap.Hutao;
|
|||||||
/// 主窗体
|
/// 主窗体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Singleton)]
|
[Injection(InjectAs.Singleton)]
|
||||||
public sealed partial class MainWindow : Window
|
public sealed partial class MainWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
private readonly WindowManager windowManager;
|
private readonly WindowManager windowManager;
|
||||||
private readonly IMessenger messenger;
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的主窗体
|
/// 构造一个新的主窗体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appDbContext">数据库上下文</param>
|
public MainWindow()
|
||||||
/// <param name="messenger">消息器</param>
|
|
||||||
public MainWindow(IMessenger messenger)
|
|
||||||
{
|
{
|
||||||
this.messenger = messenger;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
windowManager = new WindowManager(this, TitleBarView.DragArea);
|
windowManager = new WindowManager(this, TitleBarView.DragArea);
|
||||||
|
|
||||||
initializaionCompletionSource.TrySetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainWindowClosed(object sender, WindowEventArgs args)
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
messenger.Send(new MainWindowClosedMessage());
|
windowManager.Dispose();
|
||||||
|
|
||||||
windowManager?.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Message;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 主窗体关闭消息
|
|
||||||
/// </summary>
|
|
||||||
internal class MainWindowClosedMessage
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,17 @@ WM_NCRBUTTONUP
|
|||||||
// Type definition
|
// Type definition
|
||||||
MINMAXINFO
|
MINMAXINFO
|
||||||
|
|
||||||
|
|
||||||
// Comctl32
|
// Comctl32
|
||||||
DefSubclassProc
|
DefSubclassProc
|
||||||
SetWindowSubclass
|
SetWindowSubclass
|
||||||
RemoveWindowSubclass
|
RemoveWindowSubclass
|
||||||
|
|
||||||
|
// Kernel32
|
||||||
|
CreateToolhelp32Snapshot
|
||||||
|
Module32First
|
||||||
|
Module32Next
|
||||||
|
ReadProcessMemory
|
||||||
|
WriteProcessMemory
|
||||||
|
|
||||||
// User32
|
// User32
|
||||||
FindWindowEx
|
FindWindowEx
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Win32;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||||
|
using static Windows.Win32.PInvoke;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Unlocker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏帧率解锁器
|
||||||
|
/// </summary>
|
||||||
|
internal class GameFpsUnlocker : IGameFpsUnlocker
|
||||||
|
{
|
||||||
|
private readonly Process gameProcess;
|
||||||
|
|
||||||
|
private nuint fpsAddress;
|
||||||
|
private bool isValid = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的 <see cref="Unlocker"/> 对象,
|
||||||
|
/// 每个解锁器只能解锁一次原神的进程,
|
||||||
|
/// 再次解锁需要重新创建对象
|
||||||
|
/// <para/>
|
||||||
|
/// 解锁器需要在管理员模式下才能正确的完成解锁操作,
|
||||||
|
/// 非管理员模式不能解锁
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gameProcess">游戏进程</param>
|
||||||
|
/// <param name="targetFPS">目标fps</param>
|
||||||
|
public GameFpsUnlocker(Process gameProcess, int targetFPS)
|
||||||
|
{
|
||||||
|
Must.Range(targetFPS >= 30 && targetFPS <= 2000, "目标FPS超过允许值");
|
||||||
|
|
||||||
|
TargetFps = targetFPS;
|
||||||
|
this.gameProcess = gameProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int TargetFps { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay)
|
||||||
|
{
|
||||||
|
Verify.Operation(isValid, "此解锁器已经失效");
|
||||||
|
|
||||||
|
MODULEENTRY32 unityPlayer = await FindModuleAsync(findModuleDelay, findModuleLimit).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Read UnityPlayer.dll
|
||||||
|
TryReadModuleMemoryFindFpsAddress(unityPlayer);
|
||||||
|
|
||||||
|
// When player switch between scenes, we have to re adjust the fps
|
||||||
|
// So we keep a loop here
|
||||||
|
await LoopAdjustFpsAsync(adjustFpsDelay).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool UnsafeReadModuleMemory(Process process, MODULEENTRY32 entry, out Span<byte> image)
|
||||||
|
{
|
||||||
|
image = new byte[entry.modBaseSize];
|
||||||
|
fixed (byte* lpBuffer = image)
|
||||||
|
{
|
||||||
|
return ReadProcessMemory(process.SafeHandle, entry.modBaseAddr, lpBuffer, entry.modBaseSize, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe bool UnsafeWriteModuleMemory(Process process, nuint baseAddress, int write)
|
||||||
|
{
|
||||||
|
int* lpBuffer = &write;
|
||||||
|
|
||||||
|
return WriteProcessMemory(process.SafeHandle, (void*)baseAddress, lpBuffer, sizeof(int), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe MODULEENTRY32 FindModule(int processId, string moduleName)
|
||||||
|
{
|
||||||
|
using (SafeFileHandle snapshot = CreateToolhelp32Snapshot_SafeHandle(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, (uint)processId))
|
||||||
|
{
|
||||||
|
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
|
||||||
|
|
||||||
|
MODULEENTRY32 entry = StructMarshal.MODULEENTRY32();
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
// First module must be exe. Ignoring it.
|
||||||
|
for (Module32First(snapshot, ref entry); Module32Next(snapshot, ref entry);)
|
||||||
|
{
|
||||||
|
if (entry.th32ProcessID == processId && entry.szModule.AsString() == moduleName)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found ? entry : default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MODULEENTRY32> FindModuleAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit)
|
||||||
|
{
|
||||||
|
ValueStopwatch watch = ValueStopwatch.StartNew();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
MODULEENTRY32 module = FindModule(gameProcess.Id, "UnityPlayer.dll");
|
||||||
|
if (!StructMarshal.IsDefault(module))
|
||||||
|
{
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (watch.GetElapsedTime() > findModuleLimit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(findModuleDelay).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoopAdjustFpsAsync(TimeSpan adjustFpsDelay)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!gameProcess.HasExited && fpsAddress != 0)
|
||||||
|
{
|
||||||
|
UnsafeWriteModuleMemory(gameProcess, fpsAddress, TargetFps);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
fpsAddress = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(adjustFpsDelay).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void TryReadModuleMemoryFindFpsAddress(MODULEENTRY32 unityPlayer)
|
||||||
|
{
|
||||||
|
bool readOk = UnsafeReadModuleMemory(gameProcess, unityPlayer, out Span<byte> image);
|
||||||
|
Verify.Operation(readOk, "读取内存失败");
|
||||||
|
|
||||||
|
// Find FPS offset
|
||||||
|
// 7F 0F jg 0x11
|
||||||
|
// 8B 05 ? ? ? ? mov eax, dword ptr[rip+?]
|
||||||
|
int adr = image.IndexOf(new byte[] { 0x7F, 0x0F, 0x8B, 0x05 });
|
||||||
|
|
||||||
|
Must.Range(adr >= 0, "未匹配到FPS字节");
|
||||||
|
|
||||||
|
int rip = adr + 2;
|
||||||
|
int rel = Unsafe.ReadUnaligned<int>(ref image[rip + 2]);
|
||||||
|
int ofs = rip + rel + 6;
|
||||||
|
fpsAddress = (nuint)((long)unityPlayer.modBaseAddr + ofs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Unlocker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏帧率解锁器
|
||||||
|
/// </summary>
|
||||||
|
internal interface IGameFpsUnlocker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目标FPS,运行动态设置以动态更改帧率
|
||||||
|
/// </summary>
|
||||||
|
int TargetFps { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步的解锁帧数限制
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="findModuleDelay">每次查找UnityPlayer的延时,推荐100毫秒</param>
|
||||||
|
/// <param name="findModuleLimit">查找UnityPlayer的最大阈值,推荐10000毫秒</param>
|
||||||
|
/// <param name="adjustFpsDelay">每次循环调整的间隔时间,推荐2000毫秒</param>
|
||||||
|
/// <returns>解锁的结果</returns>
|
||||||
|
Task UnlockAsync(TimeSpan findModuleDelay, TimeSpan findModuleLimit, TimeSpan adjustFpsDelay);
|
||||||
|
}
|
||||||
@@ -27,9 +27,12 @@
|
|||||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||||
<StartupObject>Snap.Hutao.Program</StartupObject>
|
<StartupObject>Snap.Hutao.Program</StartupObject>
|
||||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="NativeMethods.json" />
|
||||||
|
<None Remove="NativeMethods.txt" />
|
||||||
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
|
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
|
||||||
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||||
@@ -54,6 +57,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<AdditionalFiles Include="NativeMethods.json" />
|
||||||
|
<AdditionalFiles Include="NativeMethods.txt" />
|
||||||
<AdditionalFiles Include="stylecop.json" />
|
<AdditionalFiles Include="stylecop.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -89,6 +94,10 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
|
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||||
@@ -101,7 +110,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
||||||
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
<ProjectReference Include="..\Snap.Hutao.Win32\Snap.Hutao.Win32.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||||
@@ -195,5 +203,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Web\Hoyolab\Takumi\Event\" />
|
<Folder Include="Web\Hoyolab\Takumi\Event\" />
|
||||||
|
<Folder Include="Win32\UI\WindowsAndMessaging\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<shcc:CancellablePage
|
<shc:ScopedPage
|
||||||
x:Class="Snap.Hutao.View.Page.AchievementPage"
|
x:Class="Snap.Hutao.View.Page.AchievementPage"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
|
xmlns:shc="using:Snap.Hutao.Control"
|
||||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||||
d:DataContext="{d:DesignInstance shv:AchievementViewModel}">
|
d:DataContext="{d:DesignInstance shv:AchievementViewModel}">
|
||||||
|
|
||||||
<shcc:CancellablePage.Resources>
|
<shc:ScopedPage.Resources>
|
||||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||||
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
</shcc:CancellablePage.Resources>
|
</shc:ScopedPage.Resources>
|
||||||
|
|
||||||
<mxi:Interaction.Behaviors>
|
<mxi:Interaction.Behaviors>
|
||||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||||
@@ -214,4 +214,4 @@
|
|||||||
</SplitView.Content>
|
</SplitView.Content>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
</Grid>
|
</Grid>
|
||||||
</shcc:CancellablePage>
|
</shc:ScopedPage>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
using Snap.Hutao.ViewModel;
|
using Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.View.Page;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成就页面
|
/// 成就页面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AchievementPage : CancellablePage
|
public sealed partial class AchievementPage : ScopedPage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的成就页面
|
/// 构造一个新的成就页面
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<shcc:CancellablePage
|
<shc:ScopedPage
|
||||||
x:Class="Snap.Hutao.View.Page.AnnouncementPage"
|
x:Class="Snap.Hutao.View.Page.AnnouncementPage"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -14,18 +14,16 @@
|
|||||||
xmlns:shc="using:Snap.Hutao.Control"
|
xmlns:shc="using:Snap.Hutao.Control"
|
||||||
xmlns:shca="using:Snap.Hutao.Control.Animation"
|
xmlns:shca="using:Snap.Hutao.Control.Animation"
|
||||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
|
|
||||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
<mxi:Interaction.Behaviors>
|
<mxi:Interaction.Behaviors>
|
||||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||||
</mxi:Interaction.Behaviors>
|
</mxi:Interaction.Behaviors>
|
||||||
<shcc:CancellablePage.Resources>
|
<shc:ScopedPage.Resources>
|
||||||
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
|
|
||||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||||
</shcc:CancellablePage.Resources>
|
</shc:ScopedPage.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ScrollViewer Padding="0,0,4,0">
|
<ScrollViewer Padding="0,0,4,0">
|
||||||
<ItemsControl
|
<ItemsControl
|
||||||
@@ -170,4 +168,4 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</shcc:CancellablePage>
|
</shc:ScopedPage>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
using Snap.Hutao.ViewModel;
|
using Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.View.Page;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 公告页面
|
/// 公告页面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AnnouncementPage : CancellablePage
|
public sealed partial class AnnouncementPage : ScopedPage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的公告页面
|
/// 构造一个新的公告页面
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition MaxWidth="1000"/>
|
||||||
|
<ColumnDefinition Width="auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel Margin="32,0,24,0">
|
<StackPanel Margin="32,0,24,0">
|
||||||
<sc:SettingsGroup Header="关于 胡桃">
|
<sc:SettingsGroup Header="关于 胡桃">
|
||||||
<Grid Margin="0,4,0,16">
|
<Grid Margin="0,4,0,16">
|
||||||
@@ -43,7 +48,6 @@
|
|||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
Text="Copyright © 2022 DGP Studio. All Rights Reserved."/>
|
Text="Copyright © 2022 DGP Studio. All Rights Reserved."/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<sc:Setting
|
<sc:Setting
|
||||||
Icon=""
|
Icon=""
|
||||||
@@ -53,7 +57,9 @@
|
|||||||
Icon=""
|
Icon=""
|
||||||
Header="反馈"
|
Header="反馈"
|
||||||
Description="只处理在 Github 上反馈的问题">
|
Description="只处理在 Github 上反馈的问题">
|
||||||
<HyperlinkButton Content="前往反馈" NavigateUri="http://go.hut.ao/issue"/>
|
<HyperlinkButton
|
||||||
|
Content="前往反馈"
|
||||||
|
NavigateUri="http://go.hut.ao/issue"/>
|
||||||
</sc:Setting>
|
</sc:Setting>
|
||||||
<sc:SettingExpander>
|
<sc:SettingExpander>
|
||||||
<sc:SettingExpander.Header>
|
<sc:SettingExpander.Header>
|
||||||
@@ -92,5 +98,7 @@
|
|||||||
</sc:SettingsGroup>
|
</sc:SettingsGroup>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using CommunityToolkit.Mvvm.Input;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using CommunityToolkit.WinUI.UI;
|
using CommunityToolkit.WinUI.UI;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Control.Extension;
|
using Snap.Hutao.Control.Extension;
|
||||||
using Snap.Hutao.Core.Threading;
|
using Snap.Hutao.Core.Threading;
|
||||||
using Snap.Hutao.Core.Threading.CodeAnalysis;
|
using Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||||
@@ -52,6 +52,8 @@ internal class AchievementViewModel
|
|||||||
|
|
||||||
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
||||||
|
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
private AdvancedCollectionView? achievements;
|
private AdvancedCollectionView? achievements;
|
||||||
private IList<AchievementGoal>? achievementGoals;
|
private IList<AchievementGoal>? achievementGoals;
|
||||||
private AchievementGoal? selectedAchievementGoal;
|
private AchievementGoal? selectedAchievementGoal;
|
||||||
@@ -199,11 +201,16 @@ internal class AchievementViewModel
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
{
|
{
|
||||||
if (Achievements != null && SelectedArchive != null)
|
if (Achievements != null && SelectedArchive != null)
|
||||||
{
|
{
|
||||||
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
|
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Snap.Hutao.Control.Cancellable;
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core;
|
||||||
using Snap.Hutao.Factory.Abstraction;
|
using Snap.Hutao.Factory.Abstraction;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
|
|||||||
36
src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs
Normal file
36
src/Snap.Hutao/Snap.Hutao/Win32/MemoryExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Win32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内存拓展
|
||||||
|
/// </summary>
|
||||||
|
internal static class MemoryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将 __winmdroot_Foundation_CHAR_256 转换到 字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="char256">目标字符数组</param>
|
||||||
|
/// <returns>结果字符串</returns>
|
||||||
|
public static unsafe string AsString(this MODULEENTRY32.__winmdroot_Foundation_CHAR_256 char256)
|
||||||
|
{
|
||||||
|
byte* pszModule = (byte*)&char256;
|
||||||
|
string szModule = Encoding.UTF8.GetString(pszModule, StringLength(pszModule));
|
||||||
|
return szModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe int StringLength(byte* pszStr)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
while (*pszStr++ != 0)
|
||||||
|
{
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Win32/StructMarshal.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Win32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 结构体封送
|
||||||
|
/// </summary>
|
||||||
|
internal static class StructMarshal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的 <see cref="Windows.Win32.System.Diagnostics.ToolHelp.MODULEENTRY32"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>实例</returns>
|
||||||
|
public static unsafe MODULEENTRY32 MODULEENTRY32()
|
||||||
|
{
|
||||||
|
return new() { dwSize = (uint)sizeof(MODULEENTRY32) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断结构实例是否为默认结构
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moduleEntry32">待测试的结构</param>
|
||||||
|
/// <returns>是否为默认结构</returns>
|
||||||
|
public static bool IsDefault(MODULEENTRY32 moduleEntry32)
|
||||||
|
{
|
||||||
|
return moduleEntry32.dwSize == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user