mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao
This commit is contained in:
@@ -95,5 +95,13 @@ public class CSharpLanguageFeatureTest
|
||||
Assert.AreEqual(3, count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RangeTrimLastOne()
|
||||
{
|
||||
int[] array = { 1, 2, 3, 4 };
|
||||
|
||||
Assert.AreEqual(3, array[..^1].Length);
|
||||
}
|
||||
|
||||
public static Guid UUID { get => Guid.NewGuid(); }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection;
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
@@ -24,6 +25,8 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
private readonly bool isWebView2Supported;
|
||||
private readonly string webView2Version = SH.CoreWebView2HelperVersionUndetected;
|
||||
|
||||
private bool? isElevated;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的胡桃选项
|
||||
/// </summary>
|
||||
@@ -54,16 +57,16 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
/// </summary>
|
||||
public string UserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹路径
|
||||
/// </summary>
|
||||
public string DataFolder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 安装位置
|
||||
/// </summary>
|
||||
public string InstalledLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据文件夹路径
|
||||
/// </summary>
|
||||
public string DataFolder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 本地缓存
|
||||
/// </summary>
|
||||
@@ -89,6 +92,11 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
/// </summary>
|
||||
public bool IsWebView2Supported { get => isWebView2Supported; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为提升的权限
|
||||
/// </summary>
|
||||
public bool IsElevated { get => isElevated ??= GetElevated(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public HutaoOptions Value { get => this; }
|
||||
|
||||
@@ -121,6 +129,19 @@ internal sealed class HutaoOptions : IOptions<HutaoOptions>
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
}
|
||||
|
||||
private static bool GetElevated()
|
||||
{
|
||||
#if DEBUG_AS_FAKE_ELEVATED
|
||||
return true;
|
||||
#else
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void DetectWebView2Environment(ref string webView2Version, ref bool isWebView2Supported)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -54,30 +54,13 @@ internal static class Activation
|
||||
private static readonly WeakReference<MainWindow> MainWindowReference = new(default!);
|
||||
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否提升了权限
|
||||
/// </summary>
|
||||
/// <returns>是否提升了权限</returns>
|
||||
public static bool GetElevated()
|
||||
{
|
||||
#if DEBUG_AS_FAKE_ELEVATED
|
||||
return true;
|
||||
#else
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以管理员模式重启
|
||||
/// </summary>
|
||||
/// <returns>任务</returns>
|
||||
public static async ValueTask RestartAsElevatedAsync()
|
||||
{
|
||||
if (!GetElevated())
|
||||
// if (!GetElevated())
|
||||
{
|
||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||
Process.GetCurrentProcess().Kill();
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WindowOptions WindowOptions { get => throw new NotImplementedException(); }
|
||||
public WindowOptions WindowOptions { get => windowOptions; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
||||
@@ -8,6 +8,7 @@ WM_NULL
|
||||
// Type & Enum definition
|
||||
CWMO_FLAGS
|
||||
MINMAXINFO
|
||||
LPTHREAD_START_ROUTINE
|
||||
|
||||
// COMCTL32
|
||||
DefSubclassProc
|
||||
@@ -23,11 +24,17 @@ GetDeviceCaps
|
||||
|
||||
// KERNEL32
|
||||
CreateEvent
|
||||
CreateRemoteThread
|
||||
CreateToolhelp32Snapshot
|
||||
GetModuleHandle
|
||||
GetProcAddress
|
||||
Module32First
|
||||
Module32Next
|
||||
ReadProcessMemory
|
||||
SetEvent
|
||||
VirtualAllocEx
|
||||
VirtualFreeEx
|
||||
WaitForSingleObject
|
||||
WriteProcessMemory
|
||||
|
||||
// OLE32
|
||||
|
||||
@@ -71,15 +71,14 @@ internal sealed partial class AchievementService : IAchievementService
|
||||
{
|
||||
int finished = await appDbContext.Achievements
|
||||
.Where(a => a.ArchiveId == archive.InnerId)
|
||||
|
||||
// We already filtered out incompleted achievements when save them.
|
||||
// .Where(a => (int)a.Status >= (int)Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.Where(a => (int)a.Status >= (int)Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.CountAsync()
|
||||
.ConfigureAwait(false);
|
||||
int totalCount = achievementMap.Count;
|
||||
|
||||
List<EntityAchievement> achievements = await appDbContext.Achievements
|
||||
.Where(a => a.ArchiveId == archive.InnerId)
|
||||
.Where(a => (int)a.Status >= (int)Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.OrderByDescending(a => a.Time.ToString())
|
||||
.Take(2)
|
||||
.ToListAsync()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO.Ini;
|
||||
@@ -33,6 +34,7 @@ internal sealed partial class GameService : IGameService
|
||||
private readonly PackageConverter packageConverter;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly AppOptions appOptions;
|
||||
@@ -289,7 +291,7 @@ internal sealed partial class GameService : IGameService
|
||||
|
||||
game.Start();
|
||||
|
||||
bool isAdvancedOptionsAllowed = Activation.GetElevated() && appOptions.IsAdvancedLaunchOptionsEnabled;
|
||||
bool isAdvancedOptionsAllowed = hutaoOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled;
|
||||
if (isAdvancedOptionsAllowed && launchOptions.MultipleInstances && !isfirstInstance)
|
||||
{
|
||||
ProcessInterop.DisableProtection(game, gamePath);
|
||||
|
||||
@@ -5,6 +5,11 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Memory;
|
||||
using Windows.Win32.System.Threading;
|
||||
using static Windows.Win32.PInvoke;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
|
||||
@@ -83,4 +88,68 @@ internal static class ProcessInterop
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载并注入指定路径的库
|
||||
/// </summary>
|
||||
/// <param name="hProcess">进程句柄</param>
|
||||
/// <param name="libraryPathu8">库的路径,不包含'\0'</param>
|
||||
[SuppressMessage("", "SH002")]
|
||||
public static unsafe void LoadLibraryAndInject(HANDLE hProcess, ReadOnlySpan<byte> libraryPathu8)
|
||||
{
|
||||
HINSTANCE hKernelDll = GetModuleHandle("kernel32.dll");
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
|
||||
FARPROC pLoadLibraryA = GetProcAddress(hKernelDll, libraryPathu8);
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
|
||||
void* pNativeLibraryPath = default;
|
||||
try
|
||||
{
|
||||
VIRTUAL_ALLOCATION_TYPE allocType = VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT;
|
||||
pNativeLibraryPath = VirtualAllocEx(hProcess, default, unchecked((uint)libraryPathu8.Length + 1), allocType, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
|
||||
WriteProcessMemory(hProcess, pNativeLibraryPath, libraryPathu8);
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
|
||||
LPTHREAD_START_ROUTINE lpThreadLoadLibraryA = pLoadLibraryA.CreateDelegate<LPTHREAD_START_ROUTINE>();
|
||||
HANDLE hLoadLibraryAThread = default;
|
||||
try
|
||||
{
|
||||
hLoadLibraryAThread = CreateRemoteThread(hProcess, default, 0, lpThreadLoadLibraryA, pNativeLibraryPath, 0);
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
|
||||
// What are we waiting for?
|
||||
WaitForSingleObject(hLoadLibraryAThread, 2000);
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError());
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hLoadLibraryAThread);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
VirtualFreeEx(hProcess, pNativeLibraryPath, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private static unsafe FARPROC GetProcAddress(HINSTANCE hModule, ReadOnlySpan<byte> lpProcName)
|
||||
{
|
||||
fixed (byte* lpProcNameLocal = lpProcName)
|
||||
{
|
||||
return Windows.Win32.PInvoke.GetProcAddress(hModule, new PCSTR(lpProcNameLocal));
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private static unsafe BOOL WriteProcessMemory(HANDLE hProcess, void* lpBaseAddress, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
fixed (void* lpBuffer = buffer)
|
||||
{
|
||||
return Windows.Win32.PInvoke.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, unchecked((uint)buffer.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
<ItemGroup>
|
||||
<!-- https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json -->
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.SettingsControls" Version="0.0.18" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
@@ -263,7 +263,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.3.230331000" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.3.230502000" />
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="NumberBoxMinWidth">180</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">1200</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
@@ -48,11 +51,11 @@
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<NumberBox
|
||||
Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinWidth="{StaticResource NumberBoxMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{Binding LevelMax}"
|
||||
Minimum="{Binding LevelMin}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Value="{Binding LevelCurrent, Mode=TwoWay}"/>
|
||||
<FontIcon
|
||||
Grid.Column="3"
|
||||
@@ -61,11 +64,11 @@
|
||||
Glyph=""/>
|
||||
<NumberBox
|
||||
Grid.Column="4"
|
||||
MinWidth="100"
|
||||
MinWidth="{StaticResource NumberBoxMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{Binding LevelMax}"
|
||||
Minimum="{Binding LevelMin}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Value="{Binding LevelTarget, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -95,11 +98,11 @@
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<NumberBox
|
||||
Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinWidth="{StaticResource NumberBoxMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{Binding LevelMax}"
|
||||
Minimum="{Binding LevelMin}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Value="{Binding LevelCurrent, Mode=TwoWay}"/>
|
||||
<FontIcon
|
||||
Grid.Column="3"
|
||||
@@ -108,11 +111,11 @@
|
||||
Glyph=""/>
|
||||
<NumberBox
|
||||
Grid.Column="4"
|
||||
MinWidth="100"
|
||||
MinWidth="{StaticResource NumberBoxMinWidth}"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="{Binding LevelMax}"
|
||||
Minimum="{Binding LevelMin}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Value="{Binding LevelTarget, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -508,6 +508,12 @@
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="Left">
|
||||
<StackPanel MaxWidth="320">
|
||||
<StackPanel.Resources>
|
||||
<Thickness x:Key="SettingsCardPadding">16</Thickness>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
</StackPanel.Resources>
|
||||
<shct:DescriptionTextBlock Description="{Binding Description}">
|
||||
<shct:DescriptionTextBlock.Resources>
|
||||
<Style BasedOn="{StaticResource BodyTextBlockStyle}" TargetType="TextBlock">
|
||||
@@ -522,7 +528,7 @@
|
||||
Margin="0,2,0,0"
|
||||
Padding="12,0"
|
||||
Header="{Binding Description}">
|
||||
<TextBlock Text="{Binding Parameter}"/>
|
||||
<TextBlock Margin="0,8" Text="{Binding Parameter}"/>
|
||||
</clw:SettingsCard>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsEnabled="{Binding IsElevated}">
|
||||
IsEnabled="{Binding HutaoOptions.IsElevated}">
|
||||
<ComboBox
|
||||
DisplayMemberPath="DisplayName"
|
||||
ItemsSource="{Binding KnownSchemes}"
|
||||
@@ -183,13 +183,19 @@
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenWidthDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenWidthHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<NumberBox Width="156" Value="{Binding Options.ScreenWidth, Mode=TwoWay}"/>
|
||||
<NumberBox
|
||||
Width="156"
|
||||
Padding="10,6,0,0"
|
||||
Value="{Binding Options.ScreenWidth, Mode=TwoWay}"/>
|
||||
</clw:SettingsCard>
|
||||
<clw:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenHeightDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenHeightHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<NumberBox Width="156" Value="{Binding Options.ScreenHeight, Mode=TwoWay}"/>
|
||||
<NumberBox
|
||||
Width="156"
|
||||
Padding="10,6,0,0"
|
||||
Value="{Binding Options.ScreenHeight, Mode=TwoWay}"/>
|
||||
</clw:SettingsCard>
|
||||
|
||||
<clw:SettingsCard
|
||||
@@ -208,31 +214,29 @@
|
||||
<clw:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameMultipleInstancesDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameMultipleInstancesHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsEnabled="{Binding HutaoOptions.IsElevated}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.MultipleInstances, Mode=TwoWay}"/>
|
||||
</clw:SettingsCard>
|
||||
<clw:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch
|
||||
Width="120"
|
||||
IsOn="{Binding Options.UnlockFps, Mode=TwoWay}"
|
||||
OffContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOff}"
|
||||
OnContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOn}"/>
|
||||
</clw:SettingsCard>
|
||||
<clw:SettingsCard Header="{shcm:ResourceString Name=ViewPageLaunchGameSetFpsHeader}">
|
||||
<clw:SettingsCard.Description>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewPageLaunchGameSetFpsDescription}"/>
|
||||
<TextBlock Text="{Binding Options.TargetFps}"/>
|
||||
</StackPanel>
|
||||
</clw:SettingsCard.Description>
|
||||
<Slider
|
||||
Width="400"
|
||||
Maximum="360"
|
||||
Minimum="60"
|
||||
Value="{Binding Options.TargetFps, Mode=TwoWay}"/>
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsEnabled="{Binding HutaoOptions.IsElevated}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<NumberBox
|
||||
MinWidth="156"
|
||||
Padding="10,8,0,0"
|
||||
Maximum="720"
|
||||
Minimum="60"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Value="{Binding Options.TargetFps, Mode=TwoWay}"/>
|
||||
<ToggleSwitch
|
||||
Width="120"
|
||||
IsOn="{Binding Options.UnlockFps, Mode=TwoWay}"
|
||||
OffContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOff}"
|
||||
OnContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOn}"/>
|
||||
</StackPanel>
|
||||
</clw:SettingsCard>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<Button
|
||||
Margin="16"
|
||||
HorizontalAlignment="Right"
|
||||
Click="CookieButtonClick"
|
||||
Command="{x:Bind HandleCurrentCookieCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
<WebView2 x:Name="WebView" Grid.Row="2"/>
|
||||
</Grid>
|
||||
|
||||
@@ -70,7 +70,8 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCurrentCookieAsync(CancellationToken token = default)
|
||||
[Command("HandleCurrentCookieCommand")]
|
||||
private async Task HandleCurrentCookieAsync()
|
||||
{
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://account.hoyoverse.com");
|
||||
@@ -78,13 +79,13 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control
|
||||
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||
|
||||
Cookie loginTicketCookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
string uid = await GetUidFromCookieAsync(Ioc.Default, loginTicketCookie, token).ConfigureAwait(false);
|
||||
string uid = await GetUidFromCookieAsync(Ioc.Default, loginTicketCookie).ConfigureAwait(false);
|
||||
loginTicketCookie[Cookie.LOGIN_UID] = uid;
|
||||
|
||||
// 使用 loginTicket 获取 stoken
|
||||
Response<ListWrapper<NameToken>> multiTokenResponse = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicketCookie, true, token)
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicketCookie, true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!multiTokenResponse.IsOk())
|
||||
@@ -109,11 +110,6 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CookieButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleCurrentCookieAsync().SafeForget();
|
||||
}
|
||||
|
||||
private sealed class WebApiResponse<TData>
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<Button
|
||||
Margin="16"
|
||||
HorizontalAlignment="Right"
|
||||
Click="CookieButtonClick"
|
||||
Command="{x:Bind HandleCurrentCookieCommand, Mode=OneWay}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
<WebView2 x:Name="WebView" Grid.Row="2"/>
|
||||
</Grid>
|
||||
|
||||
@@ -49,7 +49,8 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCurrentCookieAsync(CancellationToken token = default)
|
||||
[Command("HandleCurrentCookieCommand")]
|
||||
private async Task HandleCurrentCookieAsync()
|
||||
{
|
||||
CoreWebView2CookieManager manager = WebView.CoreWebView2.CookieManager;
|
||||
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync("https://user.mihoyo.com");
|
||||
@@ -57,7 +58,7 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P
|
||||
Cookie loginTicketCookie = Cookie.FromCoreWebView2Cookies(cookies);
|
||||
Response<ListWrapper<NameToken>> multiTokenResponse = await Ioc.Default
|
||||
.GetRequiredService<AuthClient>()
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicketCookie, false, token)
|
||||
.GetMultiTokenByLoginTicketAsync(loginTicketCookie, false)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!multiTokenResponse.IsOk())
|
||||
@@ -71,7 +72,7 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P
|
||||
|
||||
Response<LoginResult> loginResultResponse = await Ioc.Default
|
||||
.GetRequiredService<PassportClient>()
|
||||
.LoginBySTokenAsync(stokenV1, token)
|
||||
.LoginBySTokenAsync(stokenV1)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!loginResultResponse.IsOk())
|
||||
@@ -92,9 +93,4 @@ internal sealed partial class LoginMihoyoUserPage : Microsoft.UI.Xaml.Controls.P
|
||||
.HandleUserOptionResultAsync(result, nickname)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CookieButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleCurrentCookieAsync().SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsEnabled="{Binding IsElevated}">
|
||||
IsEnabled="{Binding HutaoOptions.IsElevated}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding Options.IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
|
||||
</clw:SettingsCard>
|
||||
<InfoBar
|
||||
|
||||
@@ -136,6 +136,6 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
|
||||
[Command("NavigateToAfdianSkuCommand")]
|
||||
private async Task NavigateToAfdianSkuAsync()
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(new(@"ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
|
||||
await Windows.System.Launcher.LaunchUriAsync("https://afdian.net/item/80d3b9decf9011edb5f452540025c377".ToUri());
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
@@ -34,6 +35,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly HutaoOptions hutaoOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IGameService gameService;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
@@ -81,6 +83,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public LaunchOptions Options { get => launchOptions; }
|
||||
|
||||
/// <summary>
|
||||
/// 胡桃选项
|
||||
/// </summary>
|
||||
public HutaoOptions HutaoOptions { get => hutaoOptions; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用选项
|
||||
/// </summary>
|
||||
@@ -91,12 +98,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 是否提权
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public bool IsElevated { get => Activation.GetElevated(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
|
||||
@@ -100,15 +100,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public ExperimentalFeaturesViewModel Experimental { get => experimental; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否提权
|
||||
/// </summary>
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public bool IsElevated
|
||||
{
|
||||
get => Activation.GetElevated();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OpenUIAsync()
|
||||
{
|
||||
|
||||
@@ -18,24 +18,18 @@ internal sealed class SpiralAbyssView
|
||||
/// <param name="idAvatarMap">Id角色映射</param>
|
||||
public SpiralAbyssView(Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.SpiralAbyss spiralAbyss, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap)
|
||||
{
|
||||
Schedule = string.Format(SH.ModelEntitySpiralAbyssScheduleFormat, spiralAbyss.ScheduleId);
|
||||
TotalBattleTimes = spiralAbyss.TotalBattleTimes;
|
||||
TotalStar = spiralAbyss.TotalStar;
|
||||
MaxFloor = spiralAbyss.MaxFloor;
|
||||
Reveals = spiralAbyss.RevealRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).ToList();
|
||||
Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap));
|
||||
Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).SingleOrDefault();
|
||||
Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).SingleOrDefault();
|
||||
TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).SingleOrDefault();
|
||||
NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).SingleOrDefault();
|
||||
EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, r.AvatarId, idAvatarMap)).SingleOrDefault();
|
||||
Floors = spiralAbyss.Floors.Select(f => new FloorView(f, idAvatarMap)).ToList();
|
||||
Floors = spiralAbyss.Floors.Select(f => new FloorView(f, idAvatarMap)).Reverse().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期
|
||||
/// </summary>
|
||||
public string Schedule { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 战斗次数
|
||||
/// </summary>
|
||||
|
||||
@@ -19,7 +19,7 @@ internal static class CoreWebView2Extension
|
||||
/// <returns>链式调用的WebView2</returns>
|
||||
public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView)
|
||||
{
|
||||
webView.Settings.UserAgent = Core.HoyolabOptions.MobileUserAgent;
|
||||
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgent;
|
||||
return webView;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ internal static class CoreWebView2Extension
|
||||
/// <returns>链式调用的WebView2</returns>
|
||||
public static CoreWebView2 SetMobileOverseaUserAgent(this CoreWebView2 webView)
|
||||
{
|
||||
webView.Settings.UserAgent = Core.HoyolabOptions.MobileUserAgentOversea;
|
||||
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgentOversea;
|
||||
return webView;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ internal class MiHoYoJSInterface
|
||||
Data = new Dictionary<string, string>()
|
||||
{
|
||||
{ "x-rpc-client_type", "5" },
|
||||
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersion },
|
||||
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", HoyolabOptions.XrpcVersion },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -121,7 +121,7 @@ internal class MiHoYoJSInterface
|
||||
/// <returns>响应</returns>
|
||||
public virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV1(JsParam param)
|
||||
{
|
||||
string salt = Core.HoyolabOptions.Salts[SaltType.LK2];
|
||||
string salt = HoyolabOptions.Salts[SaltType.LK2];
|
||||
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
string r = GetRandomString();
|
||||
string check = Core.Convert.ToMd5HexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant();
|
||||
@@ -152,7 +152,7 @@ internal class MiHoYoJSInterface
|
||||
public virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV2(JsParam<DynamicSecrect2Playload> param)
|
||||
{
|
||||
// TODO: Salt X4 for hoyolab user
|
||||
string salt = Core.HoyolabOptions.Salts[SaltType.X4];
|
||||
string salt = HoyolabOptions.Salts[SaltType.X4];
|
||||
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
int r = GetRandom();
|
||||
string b = param.Payload.Body;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Web.Bridge.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
@@ -35,8 +36,8 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface
|
||||
Data = new Dictionary<string, string>()
|
||||
{
|
||||
{ "x-rpc-client_type", "2" },
|
||||
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersionOversea },
|
||||
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", HoyolabOptions.XrpcVersionOversea },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Web.Bridge.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
@@ -26,8 +27,8 @@ internal sealed class SignInJsInterface : MiHoYoJSInterface
|
||||
Data = new Dictionary<string, string>()
|
||||
{
|
||||
{ "x-rpc-client_type", "2" },
|
||||
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersion },
|
||||
{ "x-rpc-device_id", HoyolabOptions.DeviceId },
|
||||
{ "x-rpc-app_version", HoyolabOptions.XrpcVersion },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ internal sealed class DynamicSecretHandler : DelegatingHandler
|
||||
|
||||
private static async Task ProcessRequestWithOptionsAsync(HttpRequestMessage request, DynamicSecretCreationOptions options, CancellationToken token)
|
||||
{
|
||||
string salt = Core.HoyolabOptions.Salts[options.SaltType];
|
||||
string salt = HoyolabOptions.Salts[options.SaltType];
|
||||
|
||||
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Options;
|
||||
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// 米游社选项
|
||||
@@ -36,7 +36,7 @@ internal sealed class HoyolabOptions : IOptions<HoyolabOptions>
|
||||
/// <summary>
|
||||
/// 米游社 Rpc 版本
|
||||
/// </summary>
|
||||
public const string XrpcVersion = "2.49.1";
|
||||
public const string XrpcVersion = "2.50.1";
|
||||
|
||||
/// <summary>
|
||||
/// Hoyolab Rpc 版本
|
||||
@@ -46,8 +46,8 @@ internal sealed class HoyolabOptions : IOptions<HoyolabOptions>
|
||||
// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
private static readonly ImmutableDictionary<SaltType, string> SaltsInner = new Dictionary<SaltType, string>()
|
||||
{
|
||||
[SaltType.K2] = "egBrFMO1BPBG0UX5XOuuwMRLZKwTVKRV",
|
||||
[SaltType.LK2] = "DG8lqMyc9gquwAUFc7zBS62ijQRX9XF7",
|
||||
[SaltType.K2] = "A4lPYtN0KGRVwE5M5Fm0DqQiC5VVMVM3",
|
||||
[SaltType.LK2] = "kkFiNdhyHqZ1VnDRHnU1podIvO4eiHcs",
|
||||
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
|
||||
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
|
||||
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
|
||||
@@ -48,7 +48,7 @@ internal sealed partial class PassportClient
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>登录数据</returns>
|
||||
[ApiInformation(Salt = SaltType.PROD)]
|
||||
public async Task<Response<LoginResult>> LoginBySTokenAsync(Cookie stokenV1, CancellationToken token)
|
||||
public async Task<Response<LoginResult>> LoginBySTokenAsync(Cookie stokenV1, CancellationToken token = default)
|
||||
{
|
||||
HttpResponseMessage message = await httpClient
|
||||
.SetHeader("Cookie", stokenV1.ToString())
|
||||
|
||||
@@ -51,7 +51,7 @@ internal sealed partial class AuthClient
|
||||
/// <param name="isOversea">是否为国际服</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>包含token的字典</returns>
|
||||
public async Task<Response<ListWrapper<NameToken>>> GetMultiTokenByLoginTicketAsync(Cookie cookie, bool isOversea, CancellationToken token)
|
||||
public async Task<Response<ListWrapper<NameToken>>> GetMultiTokenByLoginTicketAsync(Cookie cookie, bool isOversea, CancellationToken token = default)
|
||||
{
|
||||
string loginTicket = cookie[Cookie.LOGIN_TICKET];
|
||||
string loginUid = cookie[Cookie.LOGIN_UID];
|
||||
|
||||
Reference in New Issue
Block a user