Compare commits

..

2 Commits

Author SHA1 Message Date
DismissedLight
98c003ae77 Merge branch 'develop' into feat/1239 2024-01-02 13:18:32 +08:00
qhy040404
d26611ccf7 impl #1239 2024-01-01 20:13:11 +08:00
15 changed files with 207 additions and 363 deletions

View File

@@ -11,12 +11,6 @@ namespace Snap.Hutao.Test.IncomingFeature;
[TestClass] [TestClass]
public class GameRegistryContentTest public class GameRegistryContentTest
{ {
private static readonly JsonSerializerOptions RegistryContentSerializerOptions = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
[TestMethod] [TestMethod]
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public void GetRegistryContent() public void GetRegistryContent()
@@ -39,29 +33,28 @@ public class GameRegistryContentTest
data[valueName] = gameKey.GetValueKind(valueName) switch data[valueName] = gameKey.GetValueKind(valueName) switch
{ {
RegistryValueKind.DWord => (int)gameKey.GetValue(valueName)!, RegistryValueKind.DWord => (int)gameKey.GetValue(valueName)!,
RegistryValueKind.Binary => GetStringOrObject((byte[])gameKey.GetValue(valueName)!), RegistryValueKind.Binary => GetString((byte[])gameKey.GetValue(valueName)!),
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
}; };
} }
JsonSerializerOptions options = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
Console.WriteLine($"Subkey: {subkey}"); Console.WriteLine($"Subkey: {subkey}");
Console.WriteLine(JsonSerializer.Serialize(data, RegistryContentSerializerOptions)); Console.WriteLine(JsonSerializer.Serialize(data, options));
} }
} }
private static unsafe object GetStringOrObject(byte[] bytes) private static unsafe string GetString(byte[] bytes)
{ {
fixed (byte* pByte = bytes) fixed (byte* pByte = bytes)
{ {
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte); ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
string temp = Encoding.UTF8.GetString(span); return Encoding.UTF8.GetString(span);
if (temp.AsSpan()[0] is '{' or '[')
{
return JsonSerializer.Deserialize<JsonElement>(temp);
}
return temp;
} }
} }
} }

View File

@@ -4,6 +4,8 @@
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Core.Caching; using Snap.Hutao.Core.Caching;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Image; namespace Snap.Hutao.Control.Image;

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
internal delegate bool SpinWaitPredicate<T>(ref readonly T state);
internal static class SpinWaitPolyfill
{
public static unsafe void SpinUntil<T>(ref T state, delegate*<ref readonly T, bool> condition)
{
SpinWait spinner = default;
while (!condition(ref state))
{
spinner.SpinOnce();
}
}
}

View File

@@ -8,45 +8,123 @@ namespace Snap.Hutao.Model.Entity;
/// </summary> /// </summary>
internal sealed partial class SettingEntry internal sealed partial class SettingEntry
{ {
/// <summary>
/// 游戏路径
/// </summary>
public const string GamePath = "GamePath"; public const string GamePath = "GamePath";
public const string GamePathEntries = "GamePathEntries"; public const string GamePathEntries = "GamePathEntries";
public const string Culture = "Culture";
public const string SystemBackdropType = "SystemBackdropType";
public const string AnnouncementRegion = "AnnouncementRegion";
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
public const string DailyNoteWebhookUrl = "DailyNote.WebhookUrl";
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
public const string LaunchIsLaunchOptionsEnabled = "Launch.IsLaunchOptionsEnabled";
public const string LaunchIsExclusive = "Launch.IsExclusive";
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
public const string LaunchIsBorderless = "Launch.IsBorderless";
public const string LaunchScreenWidth = "Launch.ScreenWidth";
public const string LaunchIsScreenWidthEnabled = "Launch.IsScreenWidthEnabled";
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
public const string LaunchUnlockFps = "Launch.UnlockFps";
public const string LaunchTargetFps = "Launch.TargetFps";
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
public const string LaunchIsWindowsHDREnabled = "Launch.IsWindowsHDREnabled";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
[Obsolete("不再支持多开")]
public const string MultipleInstances = "Launch.MultipleInstances";
[Obsolete("不再使用 PowerShell")] [Obsolete("不再使用 PowerShell")]
public const string PowerShellPath = "PowerShellPath"; public const string PowerShellPath = "PowerShellPath";
/// <summary>
/// 空的历史记录卡池是否可见
/// </summary>
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
/// <summary>
/// 窗口背景类型
/// </summary>
public const string SystemBackdropType = "SystemBackdropType";
/// <summary>
/// 启用高级功能
/// </summary>
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
/// <summary>
/// 实时便笺刷新时间
/// </summary>
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
/// <summary>
/// 实时便笺提醒式通知
/// </summary>
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
/// <summary>
/// 实时便笺免打扰模式
/// </summary>
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
/// <summary>
/// 实时便笺 WebhookUrl
/// </summary>
public const string DailyNoteWebhookUrl = "DailyNote.WebhookUrl";
/// <summary>
/// 启动游戏 总开关
/// </summary>
public const string LaunchIsLaunchOptionsEnabled = "Launch.IsLaunchOptionsEnabled";
/// <summary>
/// 启动游戏 独占全屏
/// </summary>
public const string LaunchIsExclusive = "Launch.IsExclusive";
/// <summary>
/// 启动游戏 全屏
/// </summary>
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
/// <summary>
/// 启动游戏 无边框
/// </summary>
public const string LaunchIsBorderless = "Launch.IsBorderless";
/// <summary>
/// 启动游戏 宽度
/// </summary>
public const string LaunchScreenWidth = "Launch.ScreenWidth";
public const string LaunchIsScreenWidthEnabled = "Launch.IsScreenWidthEnabled";
/// <summary>
/// 启动游戏 高度
/// </summary>
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
/// <summary>
/// 启动游戏 解锁帧率
/// </summary>
public const string LaunchUnlockFps = "Launch.UnlockFps";
/// <summary>
/// 启动游戏 目标帧率
/// </summary>
public const string LaunchTargetFps = "Launch.TargetFps";
/// <summary>
/// 启动游戏 显示器编号
/// </summary>
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
/// <summary>
/// 启动游戏 多倍启动
/// </summary>
[Obsolete("不再支持多开")]
public const string MultipleInstances = "Launch.MultipleInstances";
/// <summary>
/// 语言
/// </summary>
public const string Culture = "Culture";
/// <summary>
/// 自定义极验接口
/// </summary>
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
public const string AnnouncementRegion = "AnnouncementRegion";
} }

View File

@@ -121,7 +121,7 @@
<value>胡桃 Dev {0}</value> <value>胡桃 Dev {0}</value>
</data> </data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve"> <data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃 Dev {0} [管理员]</value> <value>胡桃Dev {0} [管理员]</value>
</data> </data>
<data name="AppElevatedNameAndVersion" xml:space="preserve"> <data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [管理员]</value> <value>胡桃 {0} [管理员]</value>
@@ -2192,12 +2192,6 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve"> <data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>启用</value> <value>启用</value>
</data> </data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>充分利用支持高动态范围的显示器获得更亮、更生动、更精细的画面</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve"> <data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>请输入你的 HoYoLab Uid</value> <value>请输入你的 HoYoLab Uid</value>
</data> </data>
@@ -2753,9 +2747,6 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve"> <data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>{0} 小时后结束</value> <value>{0} 小时后结束</value>
</data> </data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>打开剪贴板失败</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve"> <data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value> <value>已复制到剪贴板</value>
</data> </data>

View File

@@ -1,10 +1,10 @@
// 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 Snap.Discord.GameSDK;
using Snap.Discord.GameSDK.ABI; using Snap.Discord.GameSDK.ABI;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.Unicode;
namespace Snap.Hutao.Service.Discord; namespace Snap.Hutao.Service.Discord;
@@ -18,94 +18,75 @@ internal static class DiscordController
private static readonly CancellationTokenSource StopTokenSource = new(); private static readonly CancellationTokenSource StopTokenSource = new();
private static readonly object SyncRoot = new(); private static readonly object SyncRoot = new();
private static long currentClientId; private static Snap.Discord.GameSDK.Discord? discordManager;
private static unsafe IDiscordCore* discordCorePtr;
private static bool isInitialized; private static bool isInitialized;
public static async ValueTask<DiscordResult> SetDefaultActivityAsync(DateTimeOffset startTime) public static async ValueTask<Result> SetDefaultActivityAsync(DateTimeOffset startTime)
{ {
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); ResetManagerOrIgnore(HutaoAppId);
return SetDefaultActivity(startTime);
static unsafe DiscordResult SetDefaultActivity(in DateTimeOffset startTime) if (discordManager is null)
{ {
ResetManagerOrIgnore(HutaoAppId); return Result.Ok;
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
activity.timestamps.start = startTime.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
} }
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.Timestamps.Start = startTime.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
} }
public static async ValueTask<DiscordResult> SetPlayingYuanShenAsync() public static async ValueTask<Result> SetPlayingYuanShenAsync()
{ {
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); ResetManagerOrIgnore(YuanshenId);
return SetPlayingYuanShen();
static unsafe DiscordResult SetPlayingYuanShen() if (discordManager is null)
{ {
ResetManagerOrIgnore(YuanshenId); return Result.Ok;
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, "原神"u8);
SetString(activity.assets.small_image, 128, "app"u8);
SetString(activity.assets.small_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
} }
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
activity.Details = SH.ServiceDiscordGameActivityDetails;
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = "原神";
activity.Assets.SmallImage = "app";
activity.Assets.SmallText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
} }
public static async ValueTask<DiscordResult> SetPlayingGenshinImpactAsync() public static async ValueTask<Result> SetPlayingGenshinImpactAsync()
{ {
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); ResetManagerOrIgnore(GenshinImpactId);
return SetPlayingGenshinImpact();
static unsafe DiscordResult SetPlayingGenshinImpact() if (discordManager is null)
{ {
ResetManagerOrIgnore(GenshinImpactId); return Result.Ok;
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, "Genshin Impact"u8);
SetString(activity.assets.small_image, 128, "app"u8);
SetString(activity.assets.small_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
} }
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
activity.Details = SH.ServiceDiscordGameActivityDetails;
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = "Genshin Impact";
activity.Assets.SmallImage = "app";
activity.Assets.SmallText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
} }
public static unsafe void Stop() public static void Stop()
{ {
if (!isInitialized) if (!isInitialized)
{ {
@@ -117,7 +98,7 @@ internal static class DiscordController
StopTokenSource.Cancel(); StopTokenSource.Cancel();
try try
{ {
discordCorePtr = default; discordManager?.Dispose();
} }
catch (SEHException) catch (SEHException)
{ {
@@ -127,30 +108,23 @@ internal static class DiscordController
private static unsafe void ResetManagerOrIgnore(long clientId) private static unsafe void ResetManagerOrIgnore(long clientId)
{ {
if (currentClientId == clientId) if (discordManager?.ClientId == clientId)
{ {
return; return;
} }
// Actually requires a discord client to be running on Windows platform. // Actually requires a discord client to be running on Windows platform.
// If not, the following creation code will throw. // If not, the following creation code will throw.
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length <= 0) if (System.Diagnostics.Process.GetProcessesByName("Discord").Length == 0)
{ {
return; return;
} }
lock (SyncRoot) lock (SyncRoot)
{ {
DiscordCreateParams @params = default; discordManager?.Dispose();
Methods.DiscordCreateParamsSetDefault(&@params); discordManager = new(clientId, CreateFlags.NoRequireDiscord);
@params.client_id = clientId; discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
@params.flags = (uint)DiscordCreateFlags.Default;
IDiscordCore* ptr = default;
Methods.DiscordCreate(3, &@params, &ptr);
currentClientId = clientId;
discordCorePtr = ptr;
discordCorePtr->set_log_hook(discordCorePtr, DiscordLogLevel.Debug, default, &DebugWriteDiscordMessage);
} }
if (isInitialized) if (isInitialized)
@@ -161,10 +135,10 @@ internal static class DiscordController
DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget(); DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget();
isInitialized = true; isInitialized = true;
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
static unsafe void DebugWriteDiscordMessage(void* state, DiscordLogLevel logLevel, sbyte* ptr) static unsafe void DebugWriteDiscordMessage(Snap.Discord.GameSDK.LogLevel logLevel, byte* ptr)
{ {
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)ptr); ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr);
string message = System.Text.Encoding.UTF8.GetString(utf8); string message = System.Text.Encoding.UTF8.GetString(utf8);
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}"); System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}");
} }
@@ -172,7 +146,7 @@ internal static class DiscordController
private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken) private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken)
{ {
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500))) using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000)))
{ {
try try
{ {
@@ -187,10 +161,15 @@ internal static class DiscordController
{ {
try try
{ {
DiscordResult result = DiscordCoreRunRunCallbacks(); discordManager?.RunCallbacks();
if (result is not DiscordResult.Ok) }
catch (ResultException ex)
{
// If result is Ok
// Maybe the connection is reset.
if (ex.Result is not Result.Ok)
{ {
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{result:D} {result}"); System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{ex.Result:D} {ex.Result}");
} }
} }
catch (SEHException ex) catch (SEHException ex)
@@ -206,70 +185,5 @@ internal static class DiscordController
{ {
} }
} }
unsafe DiscordResult DiscordCoreRunRunCallbacks()
{
if (discordCorePtr is not null)
{
return discordCorePtr->run_callbacks(discordCorePtr);
}
return DiscordResult.Ok;
}
}
private static unsafe void SetString(sbyte* reference, int length, string source)
{
Span<sbyte> sbytes = new(reference, length);
sbytes.Clear();
Utf8.FromUtf16(source.AsSpan(), MemoryMarshal.Cast<sbyte, byte>(sbytes), out _, out _);
}
private static unsafe void SetString(sbyte* reference, int length, in ReadOnlySpan<byte> source)
{
Span<sbyte> sbytes = new(reference, length);
sbytes.Clear();
source.CopyTo(MemoryMarshal.Cast<sbyte, byte>(sbytes));
}
private struct DiscordAsyncAction
{
public DiscordResult Result;
public bool IsCompleted;
}
private unsafe struct DiscordUpdateActivityAsyncAction
{
private readonly IDiscordActivityManager* activityManagerPtr;
private DiscordAsyncAction discordAsyncAction;
public DiscordUpdateActivityAsyncAction(IDiscordActivityManager* activityManagerPtr)
{
this.activityManagerPtr = activityManagerPtr;
}
public DiscordResult WaitUpdateActivity(DiscordActivity activity)
{
fixed (DiscordAsyncAction* actionPtr = &discordAsyncAction)
{
activityManagerPtr->update_activity(activityManagerPtr, &activity, actionPtr, &HandleResult);
}
SpinWaitPolyfill.SpinUntil(ref discordAsyncAction, &CheckActionCompleted);
return discordAsyncAction.Result;
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void HandleResult(void* state, DiscordResult result)
{
DiscordAsyncAction* action = (DiscordAsyncAction*)state;
action->Result = result;
action->IsCompleted = true;
}
private static bool CheckActionCompleted(ref readonly DiscordAsyncAction state)
{
return state.IsCompleted;
}
} }
} }

View File

@@ -20,8 +20,6 @@ internal static class RegistryInterop
private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166"; private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810"; private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810";
private const string WindowsHDROnValueName = "WINDOWS_HDR_ON_h3132281285";
public static bool Set(GameAccount? account) public static bool Set(GameAccount? account)
{ {
if (account is not null) if (account is not null)
@@ -58,12 +56,6 @@ internal static class RegistryInterop
} }
} }
public static void SetWindowsHDR(bool isOversea)
{
string keyName = isOversea ? OverseaKeyName : ChineseKeyName;
Registry.SetValue(keyName, WindowsHDROnValueName, 1);
}
private static (string KeyName, string ValueName) GetKeyValueName(SchemeType scheme) private static (string KeyName, string ValueName) GetKeyValueName(SchemeType scheme)
{ {
return scheme switch return scheme switch

View File

@@ -42,7 +42,6 @@ internal sealed class LaunchOptions : DbStoreOptions
private NameValue<int>? monitor; private NameValue<int>? monitor;
private bool? isMonitorEnabled; private bool? isMonitorEnabled;
private bool? isUseCloudThirdPartyMobile; private bool? isUseCloudThirdPartyMobile;
private bool? isWindowsHDREnabled;
private AspectRatio? selectedAspectRatio; private AspectRatio? selectedAspectRatio;
private bool? useStarwardPlayTimeStatistics; private bool? useStarwardPlayTimeStatistics;
private bool? setDiscordActivityWhenPlaying; private bool? setDiscordActivityWhenPlaying;
@@ -199,12 +198,6 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, value); set => SetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, value);
} }
public bool IsWindowsHDREnabled
{
get => GetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, false);
set => SetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, value);
}
public List<AspectRatio> AspectRatios { get; } = public List<AspectRatio> AspectRatios { get; } =
[ [
new(2560, 1440), new(2560, 1440),

View File

@@ -4,7 +4,6 @@
using Snap.Hutao.Core; using Snap.Hutao.Core;
using Snap.Hutao.Factory.Progress; using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Service.Discord; using Snap.Hutao.Service.Discord;
using Snap.Hutao.Service.Game.Account;
using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Game.Unlocker; using Snap.Hutao.Service.Game.Unlocker;
using System.IO; using System.IO;
@@ -62,11 +61,6 @@ internal sealed partial class GameProcessService : IGameProcessService
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName); bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
if (launchOptions.IsWindowsHDREnabled)
{
RegistryInterop.SetWindowsHDR(isOversea);
}
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing)); progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath)) using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
{ {

View File

@@ -311,7 +311,7 @@
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" /> <PackageReference Include="Snap.Discord.GameSDK" Version="1.5.0" />
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.9.0"> <PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.9.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -12,6 +12,7 @@ using Snap.Hutao.Web.Response;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Web; using System.Web;
using Windows.Foundation;
namespace Snap.Hutao.View.Dialog; namespace Snap.Hutao.View.Dialog;

View File

@@ -199,12 +199,6 @@
ItemsSource="{Binding GameAccountsView}" ItemsSource="{Binding GameAccountsView}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/> SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Border> </Border>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE7F7;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsWindowsHDREnabled, Mode=TwoWay}"/>
</cwc:SettingsCard>
<!-- 进程 --> <!-- 进程 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/> <TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>

View File

@@ -3,9 +3,7 @@
using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User; using Snap.Hutao.Service.User;
using Snap.Hutao.ViewModel.User; using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Bridge.Model; using Snap.Hutao.Web.Bridge.Model;
@@ -14,12 +12,8 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.DataSigning; using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.Auth; using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
using System.IO;
using System.Net.Http;
using System.Text; using System.Text;
using Windows.Foundation; using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
namespace Snap.Hutao.Web.Bridge; namespace Snap.Hutao.Web.Bridge;
@@ -30,7 +24,7 @@ namespace Snap.Hutao.Web.Bridge;
[SuppressMessage("", "CA1001")] [SuppressMessage("", "CA1001")]
internal class MiHoYoJSBridge internal class MiHoYoJSBridge
{ {
private const string InitializeJsInterfaceScript = """ private const string InitializeJsInterfaceScript2 = """
window.MiHoYoJSInterface = { window.MiHoYoJSInterface = {
postMessage: function(arg) { chrome.webview.postMessage(arg) }, postMessage: function(arg) { chrome.webview.postMessage(arg) },
closePage: function() { this.postMessage('{"method":"closePage"}') }, closePage: function() { this.postMessage('{"method":"closePage"}') },
@@ -45,6 +39,7 @@ internal class MiHoYoJSBridge
private const string ConvertMouseToTouchScript = """ private const string ConvertMouseToTouchScript = """
function mouseListener (e, event) { function mouseListener (e, event) {
console.log(event);
let touch = new Touch({ let touch = new Touch({
identifier: Date.now(), identifier: Date.now(),
target: e.target, target: e.target,
@@ -62,6 +57,7 @@ internal class MiHoYoJSBridge
targetTouches: [touch], targetTouches: [touch],
changedTouches: [touch], changedTouches: [touch],
}); });
console.log(touchEvent);
e.target.dispatchEvent(touchEvent); e.target.dispatchEvent(touchEvent);
} }
@@ -90,9 +86,6 @@ internal class MiHoYoJSBridge
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly ILogger<MiHoYoJSBridge> logger; private readonly ILogger<MiHoYoJSBridge> logger;
private readonly IInfoBarService infoBarService;
private readonly IClipboardProvider clipboardProvider;
private readonly HttpClient httpClient;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2WebMessageReceivedEventArgs> webMessageReceivedEventHandler; private readonly TypedEventHandler<CoreWebView2, CoreWebView2WebMessageReceivedEventArgs> webMessageReceivedEventHandler;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler; private readonly TypedEventHandler<CoreWebView2, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler;
@@ -109,9 +102,6 @@ internal class MiHoYoJSBridge
taskContext = serviceProvider.GetRequiredService<ITaskContext>(); taskContext = serviceProvider.GetRequiredService<ITaskContext>();
logger = serviceProvider.GetRequiredService<ILogger<MiHoYoJSBridge>>(); logger = serviceProvider.GetRequiredService<ILogger<MiHoYoJSBridge>>();
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
clipboardProvider = serviceProvider.GetRequiredService<IClipboardProvider>();
httpClient = serviceProvider.GetRequiredService<HttpClient>();
webMessageReceivedEventHandler = OnWebMessageReceived; webMessageReceivedEventHandler = OnWebMessageReceived;
domContentLoadedEventHandler = OnDOMContentLoaded; domContentLoadedEventHandler = OnDOMContentLoaded;
@@ -374,51 +364,8 @@ internal class MiHoYoJSBridge
return null; return null;
} }
protected virtual async ValueTask<IJsBridgeResult?> Share(JsParam<SharePayload> param) protected virtual IJsBridgeResult? Share(JsParam<SharePayload> param)
{ {
if (param.Payload.Type is "image")
{
if (param.Payload.Content.ImageUrl is { } imageUrl)
{
using (Stream stream = await httpClient.GetStreamAsync(imageUrl).ConfigureAwait(false))
{
using (InMemoryRandomAccessStream origStream = new())
{
await stream.CopyToAsync(origStream.AsStreamForWrite()).ConfigureAwait(false);
using (InMemoryRandomAccessStream imageStream = new())
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, imageStream);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(origStream);
encoder.SetSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
await encoder.FlushAsync();
await taskContext.SwitchToMainThreadAsync();
if (clipboardProvider.SetBitmap(imageStream))
{
infoBarService.Success(SH.WebBridgeShareCopyToClipboardSuccess);
}
else
{
infoBarService.Error(SH.WebBridgeShareCopyToClipboardFailed);
}
}
}
}
}
else if (param.Payload.Content.ImageBase64 is { } imageBase64)
{
await ShareImageBase64CoreAsync(imageBase64).ConfigureAwait(false);
}
}
else if (param.Payload.Type is "screenshot")
{
await taskContext.SwitchToMainThreadAsync();
string base64Json = await coreWebView2.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", """{"format":"png","captureBeyondViewport":true}""");
string? base64 = JsonDocument.Parse(base64Json).RootElement.GetProperty("data").GetString();
ArgumentNullException.ThrowIfNull(base64);
await ShareImageBase64CoreAsync(base64).ConfigureAwait(false);
}
return new JsResult<Dictionary<string, string>>() return new JsResult<Dictionary<string, string>>()
{ {
Data = new() Data = new()
@@ -428,30 +375,6 @@ internal class MiHoYoJSBridge
}; };
} }
private async ValueTask ShareImageBase64CoreAsync(string base64)
{
using (MemoryStream imageStream = new())
{
await imageStream.WriteAsync(Convert.FromBase64String(base64)).ConfigureAwait(false);
using (InMemoryRandomAccessStream stream = new())
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream.AsRandomAccessStream());
encoder.SetSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
await encoder.FlushAsync();
await taskContext.SwitchToMainThreadAsync();
if (clipboardProvider.SetBitmap(stream))
{
infoBarService.Success(SH.WebBridgeShareCopyToClipboardSuccess);
}
else
{
infoBarService.Error(SH.WebBridgeShareCopyToClipboardFailed);
}
}
}
}
protected virtual ValueTask<IJsBridgeResult?> ShowAlertDialogAsync(JsParam param) protected virtual ValueTask<IJsBridgeResult?> ShowAlertDialogAsync(JsParam param)
{ {
return ValueTask.FromException<IJsBridgeResult?>(new NotSupportedException()); return ValueTask.FromException<IJsBridgeResult?>(new NotSupportedException());
@@ -582,7 +505,7 @@ internal class MiHoYoJSBridge
"hideLoading" => null, "hideLoading" => null,
"login" => null, "login" => null,
"pushPage" => await PushPageAsync(param).ConfigureAwait(false), "pushPage" => await PushPageAsync(param).ConfigureAwait(false),
"share" => await Share(param).ConfigureAwait(false), "share" => Share(param),
"showLoading" => null, "showLoading" => null,
_ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method), _ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method),
}; };
@@ -607,7 +530,7 @@ internal class MiHoYoJSBridge
ReadOnlySpan<char> uriHostSpan = uriHost.AsSpan(); ReadOnlySpan<char> uriHostSpan = uriHost.AsSpan();
if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com")) if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com"))
{ {
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript).AsTask().SafeForget(logger); coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger);
} }
} }
} }

View File

@@ -3,23 +3,8 @@
namespace Snap.Hutao.Web.Bridge.Model; namespace Snap.Hutao.Web.Bridge.Model;
[SuppressMessage("", "SA1124")]
internal sealed class ShareContent internal sealed class ShareContent
{ {
#region Screenshot
[JsonPropertyName("preview")] [JsonPropertyName("preview")]
public bool Preview { get; set; } public bool Preview { get; set; }
}
#endregion
#region Image
[JsonPropertyName("image_url")]
public string? ImageUrl { get; set; }
[JsonPropertyName("image_base64")]
public string? ImageBase64 { get; set; }
#endregion
}

View File

@@ -3,4 +3,6 @@
namespace Snap.Hutao.Web.Request.Builder.Abstraction; namespace Snap.Hutao.Web.Request.Builder.Abstraction;
internal interface IBuilder; internal interface IBuilder
{
}