merge Win32 call to main project

This commit is contained in:
DismissedLight
2022-09-10 14:41:27 +08:00
parent e957ad07a1
commit 528a4bc0c0
27 changed files with 471 additions and 217 deletions

BIN
res/HutaoIcon.psd Normal file

Binary file not shown.

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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>
/// 指示此类支持取消任务 /// 指示此类支持取消任务

View 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();
}
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
} }
} }

View File

@@ -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
{
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
/// 构造一个新的成就页面 /// 构造一个新的成就页面

View File

@@ -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>

View File

@@ -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>
/// 构造一个新的公告页面 /// 构造一个新的公告页面

View File

@@ -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="&#xECAA;" Icon="&#xECAA;"
@@ -53,7 +57,9 @@
Icon="&#xED15;" Icon="&#xED15;"
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>

View File

@@ -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/>

View File

@@ -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;

View 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;
}
}

View 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;
}
}