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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{29209B14-A6E1-442E-9287-2C65B03C96CD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
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.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Cancellable;
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
/// <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>
|
||||
/// Unconditionally throws an <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -58,7 +58,7 @@ public class SystemBackdrop
|
||||
backdropController = new();
|
||||
|
||||
// Mica Alt
|
||||
// backdropController.Kind = MicaKind.BaseAlt;
|
||||
backdropController.Kind = MicaKind.BaseAlt;
|
||||
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
|
||||
backdropController.SetSystemBackdropConfiguration(configuration);
|
||||
|
||||
|
||||
@@ -78,24 +78,18 @@ internal class WindowSubclassManager : IDisposable
|
||||
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)
|
||||
{
|
||||
case WM_GETMINMAXINFO:
|
||||
{
|
||||
double scalingFactor = Persistence.GetScaleForWindow(hwnd);
|
||||
ref MINMAXINFO info = ref MINMAXINFO.FromPointer(lParam);
|
||||
info.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, info.ptMinTrackSize.x);
|
||||
info.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, info.ptMinTrackSize.y);
|
||||
MINMAXINFO* info = (MINMAXINFO*)lParam.Value;
|
||||
info->ptMinTrackSize.X = (int)Math.Max(MinWidth * scalingFactor, info->ptMinTrackSize.X);
|
||||
info->ptMinTrackSize.X = (int)Math.Max(MinHeight * scalingFactor, info->ptMinTrackSize.X);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
{
|
||||
return new(0);
|
||||
}
|
||||
}
|
||||
|
||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:view="using:Snap.Hutao.View"
|
||||
mc:Ignorable="d"
|
||||
Closed="MainWindowClosed">
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<view:TitleView
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Windowing;
|
||||
using Snap.Hutao.Message;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
@@ -13,32 +10,22 @@ namespace Snap.Hutao;
|
||||
/// 主窗体
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
public sealed partial class MainWindow : Window
|
||||
public sealed partial class MainWindow : Window, IDisposable
|
||||
{
|
||||
private readonly WindowManager windowManager;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private readonly TaskCompletionSource initializaionCompletionSource = new();
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public MainWindow(IMessenger messenger)
|
||||
public MainWindow()
|
||||
{
|
||||
this.messenger = messenger;
|
||||
|
||||
InitializeComponent();
|
||||
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
|
||||
MINMAXINFO
|
||||
|
||||
|
||||
// Comctl32
|
||||
DefSubclassProc
|
||||
SetWindowSubclass
|
||||
RemoveWindowSubclass
|
||||
|
||||
// Kernel32
|
||||
CreateToolhelp32Snapshot
|
||||
Module32First
|
||||
Module32Next
|
||||
ReadProcessMemory
|
||||
WriteProcessMemory
|
||||
|
||||
// User32
|
||||
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>
|
||||
<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>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NativeMethods.json" />
|
||||
<None Remove="NativeMethods.txt" />
|
||||
<None Remove="Resource\Icon\UI_BagTabIcon_Avatar.png" />
|
||||
<None Remove="Resource\Icon\UI_BagTabIcon_Weapon.png" />
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
@@ -54,6 +57,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.json" />
|
||||
<AdditionalFiles Include="NativeMethods.txt" />
|
||||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -89,6 +94,10 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<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.WindowsAppSDK" Version="1.1.4" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||
@@ -101,7 +110,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
|
||||
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="..\Snap.Hutao.Win32\Snap.Hutao.Win32.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
@@ -195,5 +203,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Web\Hoyolab\Takumi\Event\" />
|
||||
<Folder Include="Win32\UI\WindowsAndMessaging\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<shcc:CancellablePage
|
||||
<shc:ScopedPage
|
||||
x:Class="Snap.Hutao.View.Page.AchievementPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -6,7 +6,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
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:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
@@ -15,10 +15,10 @@
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
d:DataContext="{d:DesignInstance shv:AchievementViewModel}">
|
||||
|
||||
<shcc:CancellablePage.Resources>
|
||||
<shc:ScopedPage.Resources>
|
||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
</shcc:CancellablePage.Resources>
|
||||
</shc:ScopedPage.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
@@ -214,4 +214,4 @@
|
||||
</SplitView.Content>
|
||||
</SplitView>
|
||||
</Grid>
|
||||
</shcc:CancellablePage>
|
||||
</shc:ScopedPage>
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.View.Page;
|
||||
/// <summary>
|
||||
/// 成就页面
|
||||
/// </summary>
|
||||
public sealed partial class AchievementPage : CancellablePage
|
||||
public sealed partial class AchievementPage : ScopedPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的成就页面
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<shcc:CancellablePage
|
||||
<shc:ScopedPage
|
||||
x:Class="Snap.Hutao.View.Page.AnnouncementPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -14,18 +14,16 @@
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shca="using:Snap.Hutao.Control.Animation"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shcc:CancellablePage.Resources>
|
||||
<shc:ScopedPage.Resources>
|
||||
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
|
||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
</shcc:CancellablePage.Resources>
|
||||
</shc:ScopedPage.Resources>
|
||||
<Grid>
|
||||
<ScrollViewer Padding="0,0,4,0">
|
||||
<ItemsControl
|
||||
@@ -170,4 +168,4 @@
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</shcc:CancellablePage>
|
||||
</shc:ScopedPage>
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.View.Page;
|
||||
/// <summary>
|
||||
/// 公告页面
|
||||
/// </summary>
|
||||
public sealed partial class AnnouncementPage : CancellablePage
|
||||
public sealed partial class AnnouncementPage : ScopedPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的公告页面
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
<ScrollViewer>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MaxWidth="1000"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="32,0,24,0">
|
||||
<sc:SettingsGroup Header="关于 胡桃">
|
||||
<Grid Margin="0,4,0,16">
|
||||
@@ -43,7 +48,6 @@
|
||||
VerticalAlignment="Bottom"
|
||||
Text="Copyright © 2022 DGP Studio. All Rights Reserved."/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
@@ -53,7 +57,9 @@
|
||||
Icon=""
|
||||
Header="反馈"
|
||||
Description="只处理在 Github 上反馈的问题">
|
||||
<HyperlinkButton Content="前往反馈" NavigateUri="http://go.hut.ao/issue"/>
|
||||
<HyperlinkButton
|
||||
Content="前往反馈"
|
||||
NavigateUri="http://go.hut.ao/issue"/>
|
||||
</sc:Setting>
|
||||
<sc:SettingExpander>
|
||||
<sc:SettingExpander.Header>
|
||||
@@ -92,5 +98,7 @@
|
||||
</sc:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
|
||||
@@ -6,7 +6,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.UI;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Core.Threading.CodeAnalysis;
|
||||
@@ -52,6 +52,8 @@ internal class AchievementViewModel
|
||||
|
||||
private readonly TaskCompletionSource<bool> openUICompletionSource = new();
|
||||
|
||||
private bool disposed;
|
||||
|
||||
private AdvancedCollectionView? achievements;
|
||||
private IList<AchievementGoal>? achievementGoals;
|
||||
private AchievementGoal? selectedAchievementGoal;
|
||||
@@ -199,11 +201,16 @@ internal class AchievementViewModel
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (Achievements != null && SelectedArchive != null)
|
||||
{
|
||||
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Snap.Hutao.Control.Cancellable;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.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