mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98c003ae77 | ||
|
|
d26611ccf7 |
@@ -11,12 +11,6 @@ namespace Snap.Hutao.Test.IncomingFeature;
|
||||
[TestClass]
|
||||
public class GameRegistryContentTest
|
||||
{
|
||||
private static readonly JsonSerializerOptions RegistryContentSerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
[TestMethod]
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void GetRegistryContent()
|
||||
@@ -39,29 +33,28 @@ public class GameRegistryContentTest
|
||||
data[valueName] = gameKey.GetValueKind(valueName) switch
|
||||
{
|
||||
RegistryValueKind.DWord => (int)gameKey.GetValue(valueName)!,
|
||||
RegistryValueKind.Binary => GetStringOrObject((byte[])gameKey.GetValue(valueName)!),
|
||||
RegistryValueKind.Binary => GetString((byte[])gameKey.GetValue(valueName)!),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
JsonSerializerOptions options = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
|
||||
string temp = Encoding.UTF8.GetString(span);
|
||||
|
||||
if (temp.AsSpan()[0] is '{' or '[')
|
||||
{
|
||||
return JsonSerializer.Deserialize<JsonElement>(temp);
|
||||
}
|
||||
|
||||
return temp;
|
||||
return Encoding.UTF8.GetString(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Web;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,45 +8,123 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
internal sealed partial class SettingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public const string GamePath = "GamePath";
|
||||
|
||||
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")]
|
||||
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";
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 Dev {0} [管理员]</value>
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理员]</value>
|
||||
@@ -2192,12 +2192,6 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>启用</value>
|
||||
</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">
|
||||
<value>请输入你的 HoYoLab Uid</value>
|
||||
</data>
|
||||
@@ -2753,9 +2747,6 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>{0} 小时后结束</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>打开剪贴板失败</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Discord.GameSDK;
|
||||
using Snap.Discord.GameSDK.ABI;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Snap.Hutao.Service.Discord;
|
||||
|
||||
@@ -18,94 +18,75 @@ internal static class DiscordController
|
||||
private static readonly CancellationTokenSource StopTokenSource = new();
|
||||
private static readonly object SyncRoot = new();
|
||||
|
||||
private static long currentClientId;
|
||||
private static unsafe IDiscordCore* discordCorePtr;
|
||||
private static Snap.Discord.GameSDK.Discord? discordManager;
|
||||
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);
|
||||
return SetDefaultActivity(startTime);
|
||||
ResetManagerOrIgnore(HutaoAppId);
|
||||
|
||||
static unsafe DiscordResult SetDefaultActivity(in DateTimeOffset startTime)
|
||||
if (discordManager is null)
|
||||
{
|
||||
ResetManagerOrIgnore(HutaoAppId);
|
||||
|
||||
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);
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
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);
|
||||
return SetPlayingYuanShen();
|
||||
ResetManagerOrIgnore(YuanshenId);
|
||||
|
||||
static unsafe DiscordResult SetPlayingYuanShen()
|
||||
if (discordManager is null)
|
||||
{
|
||||
ResetManagerOrIgnore(YuanshenId);
|
||||
|
||||
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);
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
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);
|
||||
return SetPlayingGenshinImpact();
|
||||
ResetManagerOrIgnore(GenshinImpactId);
|
||||
|
||||
static unsafe DiscordResult SetPlayingGenshinImpact()
|
||||
if (discordManager is null)
|
||||
{
|
||||
ResetManagerOrIgnore(GenshinImpactId);
|
||||
|
||||
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);
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -117,7 +98,7 @@ internal static class DiscordController
|
||||
StopTokenSource.Cancel();
|
||||
try
|
||||
{
|
||||
discordCorePtr = default;
|
||||
discordManager?.Dispose();
|
||||
}
|
||||
catch (SEHException)
|
||||
{
|
||||
@@ -127,30 +108,23 @@ internal static class DiscordController
|
||||
|
||||
private static unsafe void ResetManagerOrIgnore(long clientId)
|
||||
{
|
||||
if (currentClientId == clientId)
|
||||
if (discordManager?.ClientId == clientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually requires a discord client to be running on Windows platform.
|
||||
// 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;
|
||||
}
|
||||
|
||||
lock (SyncRoot)
|
||||
{
|
||||
DiscordCreateParams @params = default;
|
||||
Methods.DiscordCreateParamsSetDefault(&@params);
|
||||
@params.client_id = clientId;
|
||||
@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);
|
||||
discordManager?.Dispose();
|
||||
discordManager = new(clientId, CreateFlags.NoRequireDiscord);
|
||||
discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
|
||||
}
|
||||
|
||||
if (isInitialized)
|
||||
@@ -161,10 +135,10 @@ internal static class DiscordController
|
||||
DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget();
|
||||
isInitialized = true;
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
static unsafe void DebugWriteDiscordMessage(void* state, DiscordLogLevel logLevel, sbyte* ptr)
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
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);
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}");
|
||||
}
|
||||
@@ -172,7 +146,7 @@ internal static class DiscordController
|
||||
|
||||
private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)))
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000)))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -187,10 +161,15 @@ internal static class DiscordController
|
||||
{
|
||||
try
|
||||
{
|
||||
DiscordResult result = DiscordCoreRunRunCallbacks();
|
||||
if (result is not DiscordResult.Ok)
|
||||
discordManager?.RunCallbacks();
|
||||
}
|
||||
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)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,6 @@ internal static class RegistryInterop
|
||||
private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
|
||||
private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810";
|
||||
|
||||
private const string WindowsHDROnValueName = "WINDOWS_HDR_ON_h3132281285";
|
||||
|
||||
public static bool Set(GameAccount? account)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return scheme switch
|
||||
|
||||
@@ -42,7 +42,6 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
private NameValue<int>? monitor;
|
||||
private bool? isMonitorEnabled;
|
||||
private bool? isUseCloudThirdPartyMobile;
|
||||
private bool? isWindowsHDREnabled;
|
||||
private AspectRatio? selectedAspectRatio;
|
||||
private bool? useStarwardPlayTimeStatistics;
|
||||
private bool? setDiscordActivityWhenPlaying;
|
||||
@@ -199,12 +198,6 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
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; } =
|
||||
[
|
||||
new(2560, 1440),
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Game.Account;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.IO;
|
||||
@@ -62,11 +61,6 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
|
||||
|
||||
if (launchOptions.IsWindowsHDREnabled)
|
||||
{
|
||||
RegistryInterop.SetWindowsHDR(isOversea);
|
||||
}
|
||||
|
||||
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
|
||||
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
|
||||
{
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
||||
<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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -12,6 +12,7 @@ using Snap.Hutao.Web.Response;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
|
||||
@@ -199,12 +199,6 @@
|
||||
ItemsSource="{Binding GameAccountsView}"
|
||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||
</Border>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsWindowsHDREnabled, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<!-- 进程 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
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.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
@@ -30,7 +24,7 @@ namespace Snap.Hutao.Web.Bridge;
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal class MiHoYoJSBridge
|
||||
{
|
||||
private const string InitializeJsInterfaceScript = """
|
||||
private const string InitializeJsInterfaceScript2 = """
|
||||
window.MiHoYoJSInterface = {
|
||||
postMessage: function(arg) { chrome.webview.postMessage(arg) },
|
||||
closePage: function() { this.postMessage('{"method":"closePage"}') },
|
||||
@@ -45,6 +39,7 @@ internal class MiHoYoJSBridge
|
||||
|
||||
private const string ConvertMouseToTouchScript = """
|
||||
function mouseListener (e, event) {
|
||||
console.log(event);
|
||||
let touch = new Touch({
|
||||
identifier: Date.now(),
|
||||
target: e.target,
|
||||
@@ -62,6 +57,7 @@ internal class MiHoYoJSBridge
|
||||
targetTouches: [touch],
|
||||
changedTouches: [touch],
|
||||
});
|
||||
console.log(touchEvent);
|
||||
e.target.dispatchEvent(touchEvent);
|
||||
}
|
||||
|
||||
@@ -90,9 +86,6 @@ internal class MiHoYoJSBridge
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ITaskContext taskContext;
|
||||
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, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler;
|
||||
@@ -109,9 +102,6 @@ internal class MiHoYoJSBridge
|
||||
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
logger = serviceProvider.GetRequiredService<ILogger<MiHoYoJSBridge>>();
|
||||
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
||||
clipboardProvider = serviceProvider.GetRequiredService<IClipboardProvider>();
|
||||
httpClient = serviceProvider.GetRequiredService<HttpClient>();
|
||||
|
||||
webMessageReceivedEventHandler = OnWebMessageReceived;
|
||||
domContentLoadedEventHandler = OnDOMContentLoaded;
|
||||
@@ -374,51 +364,8 @@ internal class MiHoYoJSBridge
|
||||
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>>()
|
||||
{
|
||||
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)
|
||||
{
|
||||
return ValueTask.FromException<IJsBridgeResult?>(new NotSupportedException());
|
||||
@@ -582,7 +505,7 @@ internal class MiHoYoJSBridge
|
||||
"hideLoading" => null,
|
||||
"login" => null,
|
||||
"pushPage" => await PushPageAsync(param).ConfigureAwait(false),
|
||||
"share" => await Share(param).ConfigureAwait(false),
|
||||
"share" => Share(param),
|
||||
"showLoading" => null,
|
||||
_ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method),
|
||||
};
|
||||
@@ -607,7 +530,7 @@ internal class MiHoYoJSBridge
|
||||
ReadOnlySpan<char> uriHostSpan = uriHost.AsSpan();
|
||||
if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com"))
|
||||
{
|
||||
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript).AsTask().SafeForget(logger);
|
||||
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,8 @@
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class ShareContent
|
||||
{
|
||||
#region Screenshot
|
||||
|
||||
[JsonPropertyName("preview")]
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,6 @@
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
|
||||
internal interface IBuilder;
|
||||
internal interface IBuilder
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user