mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
22 Commits
windows-gr
...
fix/new_da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59e94fed3 | ||
|
|
7da778699b | ||
|
|
5bfc790ea2 | ||
|
|
fc13b85739 | ||
|
|
df999dbf51 | ||
|
|
688562c1dd | ||
|
|
051a115f84 | ||
|
|
b3d75f9fa5 | ||
|
|
0b90bdaa42 | ||
|
|
77067d27d0 | ||
|
|
e04542606e | ||
|
|
5be958ff64 | ||
|
|
a57933388d | ||
|
|
86c6c9574b | ||
|
|
47e451df2f | ||
|
|
c8592c798b | ||
|
|
5fad960b20 | ||
|
|
44ba0a90a6 | ||
|
|
883c1ca95f | ||
|
|
1a29908e5d | ||
|
|
3e9edd2f62 | ||
|
|
f9c18d2555 |
@@ -48,6 +48,8 @@ public sealed partial class App : Application
|
||||
/// <param name="serviceProvider">服务提供器</param>
|
||||
public App(IServiceProvider serviceProvider)
|
||||
{
|
||||
// DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
||||
|
||||
// Load app resource
|
||||
InitializeComponent();
|
||||
activation = serviceProvider.GetRequiredService<IActivation>();
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
<x:String x:Key="UI_Icon_Intee_Explore_1">https://api.snapgenshin.com/static/raw/Bg/UI_Icon_Intee_Explore_1.png</x:String>
|
||||
<x:String x:Key="UI_ImgSign_ItemIcon">https://api.snapgenshin.com/static/raw/Bg/UI_ImgSign_ItemIcon.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_None">https://api.snapgenshin.com/static/raw/Bg/UI_ItemIcon_None.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Events_Proce">https://api.snapgenshin.com/static/raw/Bg/UI_MarkQuest_Events_Proce.png</x:String>
|
||||
<x:String x:Key="UI_MarkTower">https://api.snapgenshin.com/static/raw/Bg/UI_MarkTower.png</x:String>
|
||||
|
||||
<!-- Mark -->
|
||||
<x:String x:Key="UI_MarkQuest_Events_Proce">https://api.snapgenshin.com/static/raw/Mark/UI_MarkQuest_Events_Proce.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Events_Start">https://api.snapgenshin.com/static/raw/Mark/UI_MarkQuest_Events_Start.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Main_Proce">https://api.snapgenshin.com/static/raw/Mark/UI_MarkQuest_Main_Proce.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Main_Start">https://api.snapgenshin.com/static/raw/Mark/UI_MarkQuest_Main_Start.png</x:String>
|
||||
<x:String x:Key="UI_MarkTower">https://api.snapgenshin.com/static/raw/Mark/UI_MarkTower.png</x:String>
|
||||
|
||||
<!-- ItemIcon -->
|
||||
<x:String x:Key="UI_ItemIcon_201">https://api.snapgenshin.com/static/raw/ItemIcon/UI_ItemIcon_201.png</x:String>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 支持Md5转换
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal static class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取字符串的MD5计算结果
|
||||
/// </summary>
|
||||
/// <param name="source">源字符串</param>
|
||||
/// <returns>计算的结果</returns>
|
||||
public static string ToMd5HexString(string source)
|
||||
{
|
||||
byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(source));
|
||||
return System.Convert.ToHexString(hash);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,12 @@ internal sealed class HutaoException : Exception
|
||||
throw new InvalidCastException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static InvalidOperationException InvalidOperation(string message, Exception? innerException = default)
|
||||
{
|
||||
throw new InvalidOperationException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static NotSupportedException NotSupported(string? message = default, Exception? innerException = default)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Snap.Hutao.Win32.UI.Shell;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
using static Snap.Hutao.Win32.Ole32;
|
||||
using static Snap.Hutao.Win32.Shell32;
|
||||
|
||||
@@ -8,12 +8,17 @@ namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
internal static class Hash
|
||||
{
|
||||
public static string SHA1HexString(string input)
|
||||
public static unsafe string SHA1HexString(string input)
|
||||
{
|
||||
return HashCore(System.Convert.ToHexString, SHA1.HashData, Encoding.UTF8.GetBytes, input);
|
||||
return HashCore(Convert.ToHexString, SHA1.HashData, Encoding.UTF8.GetBytes, input);
|
||||
}
|
||||
|
||||
private static TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input)
|
||||
public static unsafe string MD5HexString(string input)
|
||||
{
|
||||
return HashCore(Convert.ToHexString, System.Security.Cryptography.MD5.HashData, Encoding.UTF8.GetBytes, input);
|
||||
}
|
||||
|
||||
private static unsafe TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input)
|
||||
{
|
||||
return resultConverter(hashMethod(bytesConverter(input)));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ internal static class MD5
|
||||
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
|
||||
{
|
||||
byte[] bytes = await System.Security.Cryptography.MD5.HashDataAsync(stream, token).ConfigureAwait(false);
|
||||
return System.Convert.ToHexString(bytes);
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,6 @@ internal static class SHA256
|
||||
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
|
||||
{
|
||||
byte[] bytes = await System.Security.Cryptography.SHA256.HashDataAsync(stream, token).ConfigureAwait(false);
|
||||
return System.Convert.ToHexString(bytes);
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
}
|
||||
27
src/Snap.Hutao/Snap.Hutao/Core/ReadOnlySpan2D.cs
Normal file
27
src/Snap.Hutao/Snap.Hutao/Core/ReadOnlySpan2D.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Core;
|
||||
|
||||
internal readonly ref struct ReadOnlySpan2D<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
private readonly ref T reference;
|
||||
private readonly int length;
|
||||
private readonly int columns;
|
||||
|
||||
public unsafe ReadOnlySpan2D(void* pointer, int length, int columns)
|
||||
{
|
||||
reference = ref *(T*)pointer;
|
||||
this.length = length;
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> this[int row]
|
||||
{
|
||||
get => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref reference, row * columns), columns);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Win32;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
@@ -50,7 +51,7 @@ internal sealed class RuntimeOptions
|
||||
{
|
||||
string userName = Environment.UserName;
|
||||
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
|
||||
return Convert.ToMd5HexString($"{userName}{machineGuid}");
|
||||
return Hash.MD5HexString($"{userName}{machineGuid}");
|
||||
});
|
||||
|
||||
private readonly LazySlim<(string Version, bool Supported)> lazyWebViewEnvironment = new(() =>
|
||||
|
||||
@@ -67,7 +67,7 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
||||
IUnknownMarshal.Release(pPersistFile);
|
||||
}
|
||||
|
||||
IUnknownMarshal.Release(pShellLink);
|
||||
uint value = IUnknownMarshal.Release(pShellLink);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -32,6 +32,7 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
// TODO: the order here is not sync with unity.
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "ChapterId",
|
||||
"Documentation": "章节 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "ChapterGroupId",
|
||||
"Documentation": "章节分组 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "EquipAffixId",
|
||||
"Documentation": "装备属性 Id",
|
||||
@@ -113,6 +125,12 @@
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "QuestId",
|
||||
"Documentation": "任务 Id",
|
||||
"Equatable": true,
|
||||
"EqualityOperators": true
|
||||
},
|
||||
{
|
||||
"Name": "ReliquaryLevel",
|
||||
"Documentation": "圣遗物等级 1 - 21",
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.DailyNote;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
@@ -53,6 +55,9 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteE
|
||||
/// </summary>
|
||||
public DailyNote? DailyNote { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public DailyNoteArchonQuestView ArchonQuestView { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新时间
|
||||
/// </summary>
|
||||
@@ -128,4 +133,4 @@ internal sealed class DailyNoteEntry : ObservableObject, IMappingFrom<DailyNoteE
|
||||
other.ExpeditionNotifySuppressed = ExpeditionNotifySuppressed;
|
||||
other.OnPropertyChanged(nameof(ExpeditionNotifySuppressed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,42 @@
|
||||
|
||||
namespace Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
internal enum QuestType
|
||||
internal enum QuestType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Archon Quest 魔神任务
|
||||
/// </summary>
|
||||
AQ,
|
||||
|
||||
/// <summary>
|
||||
/// Fractions Quest 帮派任务
|
||||
/// </summary>
|
||||
FQ,
|
||||
|
||||
/// <summary>
|
||||
/// Legend Quest 传说任务
|
||||
/// </summary>
|
||||
LQ,
|
||||
|
||||
/// <summary>
|
||||
/// Event Quest 活动任务
|
||||
/// </summary>
|
||||
EQ,
|
||||
|
||||
/// <summary>
|
||||
/// Daily Quest 日常任务
|
||||
/// </summary>
|
||||
DQ,
|
||||
|
||||
/// <summary>
|
||||
/// Indescribable Quest 不可描述的任务?
|
||||
/// </summary>
|
||||
IQ,
|
||||
|
||||
VQ,
|
||||
|
||||
/// <summary>
|
||||
/// World Quest 世界任务
|
||||
/// </summary>
|
||||
WQ,
|
||||
}
|
||||
34
src/Snap.Hutao/Snap.Hutao/Model/Metadata/Chapter.cs
Normal file
34
src/Snap.Hutao/Snap.Hutao/Model/Metadata/Chapter.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata;
|
||||
|
||||
internal sealed class Chapter
|
||||
{
|
||||
public ChapterId Id { get; set; }
|
||||
|
||||
public ChapterGroupId GroupId { get; set; }
|
||||
|
||||
public QuestId BeginQuestId { get; set; }
|
||||
|
||||
public QuestId EndQuestId { get; set; }
|
||||
|
||||
public uint NeedPlayerLevel { get; set; }
|
||||
|
||||
public string Number { get; set; } = default!;
|
||||
|
||||
public string Title { get; set; } = default!;
|
||||
|
||||
public string Icon { get; set; } = default!;
|
||||
|
||||
public string ImageTitle { get; set; } = default!;
|
||||
|
||||
public string SerialNumberIcon { get; set; } = default!;
|
||||
|
||||
public City CityId { get; set; }
|
||||
|
||||
public QuestType QuestType { get; set; }
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Model.Metadata;
|
||||
// CityTaskOpenExcelConfig
|
||||
internal enum City : uint
|
||||
{
|
||||
None = 0,
|
||||
Mondstadt = 1,
|
||||
Liyue = 2,
|
||||
Inazuma = 3,
|
||||
|
||||
@@ -34,8 +34,6 @@ internal sealed class Material : DisplayItem
|
||||
/// <returns>是否为物品栏物品</returns>
|
||||
public bool IsInventoryItem()
|
||||
{
|
||||
// TODO: Add a pre-filtered metadata set to check if it's an inventory item
|
||||
|
||||
// 原质
|
||||
if (Id == 112001U)
|
||||
{
|
||||
|
||||
@@ -3044,6 +3044,9 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestChapterFinished" xml:space="preserve">
|
||||
<value>所有魔神任务已完成</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>全部完成</value>
|
||||
</data>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.DailyNote;
|
||||
|
||||
internal class DailyNoteMetadataContext : IMetadataContext,
|
||||
IMetadataListChapterSource
|
||||
{
|
||||
public List<Chapter> Chapters { get; set; } = default!;
|
||||
}
|
||||
@@ -5,7 +5,11 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.ViewModel.DailyNote;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
||||
@@ -50,6 +54,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
DailyNoteEntry newEntry = DailyNoteEntry.From(userAndUid);
|
||||
|
||||
Web.Response.Response<WebDailyNote> dailyNoteResponse;
|
||||
DailyNoteMetadataContext context;
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
IGameRecordClient gameRecordClient = scope.ServiceProvider
|
||||
@@ -59,6 +64,8 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
dailyNoteResponse = await gameRecordClient
|
||||
.GetDailyNoteAsync(userAndUid, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
context = await scope.GetRequiredService<IMetadataService>().GetContextAsync<DailyNoteMetadataContext>(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (dailyNoteResponse.IsOk())
|
||||
@@ -67,6 +74,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
|
||||
newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
|
||||
newEntry.ArchonQuestView = DailyNoteArchonQuestView.Create(newEntry.DailyNote, context.Chapters);
|
||||
await dailyNoteDbService.AddDailyNoteEntryAsync(newEntry, token).ConfigureAwait(false);
|
||||
|
||||
newEntry.User = userAndUid.User;
|
||||
@@ -83,9 +91,18 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
await userService.GetRoleCollectionAsync().ConfigureAwait(false);
|
||||
await RefreshDailyNotesCoreAsync(forceRefresh, token).ConfigureAwait(false);
|
||||
|
||||
List<DailyNoteEntry> entryList = await dailyNoteDbService.GetDailyNoteEntryListIncludingUserAsync(token).ConfigureAwait(false);
|
||||
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
|
||||
entries = entryList.ToObservableCollection();
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
DailyNoteMetadataContext context = await scope.GetRequiredService<IMetadataService>().GetContextAsync<DailyNoteMetadataContext>(token).ConfigureAwait(false);
|
||||
|
||||
List<DailyNoteEntry> entryList = await dailyNoteDbService.GetDailyNoteEntryListIncludingUserAsync(token).ConfigureAwait(false);
|
||||
entryList.ForEach(entry =>
|
||||
{
|
||||
entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid);
|
||||
entry.ArchonQuestView = DailyNoteArchonQuestView.Create(entry.DailyNote, context.Chapters);
|
||||
});
|
||||
entries = entryList.ToObservableCollection();
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
@@ -159,4 +176,4 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.Graphics.Gdi;
|
||||
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
using Windows.Graphics.Capture;
|
||||
using Windows.Graphics.DirectX;
|
||||
using Windows.Graphics.DirectX.Direct3D11;
|
||||
using static Snap.Hutao.Win32.Gdi32;
|
||||
using static Snap.Hutao.Win32.User32;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||
|
||||
internal readonly struct GameScreenCaptureContext
|
||||
{
|
||||
public readonly GraphicsCaptureItem Item;
|
||||
|
||||
private readonly IDirect3DDevice direct3DDevice;
|
||||
private readonly HWND hwnd;
|
||||
|
||||
public GameScreenCaptureContext(IDirect3DDevice direct3DDevice, HWND hwnd)
|
||||
{
|
||||
this.direct3DDevice = direct3DDevice;
|
||||
this.hwnd = hwnd;
|
||||
|
||||
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out Item);
|
||||
}
|
||||
|
||||
public Direct3D11CaptureFramePool CreatePool()
|
||||
{
|
||||
return Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
|
||||
}
|
||||
|
||||
public void RecreatePool(Direct3D11CaptureFramePool framePool)
|
||||
{
|
||||
framePool.Recreate(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
|
||||
}
|
||||
|
||||
public GraphicsCaptureSession CreateSession(Direct3D11CaptureFramePool framePool)
|
||||
{
|
||||
GraphicsCaptureSession session = framePool.CreateCaptureSession(Item);
|
||||
session.IsCursorCaptureEnabled = false;
|
||||
session.IsBorderRequired = false;
|
||||
return session;
|
||||
}
|
||||
|
||||
private static DirectXPixelFormat DeterminePixelFormat(HWND hwnd)
|
||||
{
|
||||
HDC hdc = GetDC(hwnd);
|
||||
if (hdc != HDC.NULL)
|
||||
{
|
||||
int bitsPerPixel = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.BITSPIXEL);
|
||||
_ = ReleaseDC(hwnd, hdc);
|
||||
if (bitsPerPixel >= 32)
|
||||
{
|
||||
return DirectXPixelFormat.R16G16B16A16Float;
|
||||
}
|
||||
}
|
||||
|
||||
return DirectXPixelFormat.B8G8R8A8UIntNormalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||
|
||||
internal sealed class GameScreenCaptureMemoryPool : MemoryPool<byte>
|
||||
{
|
||||
private static LazySlim<GameScreenCaptureMemoryPool> lazyShared = new(() => new());
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
private readonly LinkedList<GameScreenCaptureBuffer> unrentedBuffers = [];
|
||||
private readonly LinkedList<GameScreenCaptureBuffer> rentedBuffers = [];
|
||||
|
||||
private int bufferCount;
|
||||
|
||||
public new static GameScreenCaptureMemoryPool Shared { get => lazyShared.Value; }
|
||||
|
||||
public override int MaxBufferSize { get => Array.MaxLength; }
|
||||
|
||||
public int BufferCount { get => bufferCount; }
|
||||
|
||||
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(minBufferSize, 0);
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
foreach (GameScreenCaptureBuffer buffer in unrentedBuffers)
|
||||
{
|
||||
if (buffer.Memory.Length >= minBufferSize)
|
||||
{
|
||||
unrentedBuffers.Remove(buffer);
|
||||
rentedBuffers.AddLast(buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
GameScreenCaptureBuffer newBuffer = new(this, minBufferSize);
|
||||
rentedBuffers.AddLast(newBuffer);
|
||||
++bufferCount;
|
||||
return newBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (rentedBuffers.Count > 0)
|
||||
{
|
||||
HutaoException.InvalidOperation("There are still rented buffers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GameScreenCaptureBuffer : IMemoryOwner<byte>
|
||||
{
|
||||
private readonly GameScreenCaptureMemoryPool pool;
|
||||
private readonly byte[] buffer;
|
||||
|
||||
public GameScreenCaptureBuffer(GameScreenCaptureMemoryPool pool, int bufferSize)
|
||||
{
|
||||
this.pool = pool;
|
||||
buffer = GC.AllocateUninitializedArray<byte>(bufferSize);
|
||||
}
|
||||
|
||||
public Memory<byte> Memory { get => buffer.AsMemory(); }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (pool.syncRoot)
|
||||
{
|
||||
pool.rentedBuffers.Remove(this);
|
||||
pool.unrentedBuffers.AddLast(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.Graphics.Direct3D;
|
||||
using Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
using Snap.Hutao.Win32.Graphics.Dxgi;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Windows.Graphics.Capture;
|
||||
using Windows.Graphics.DirectX.Direct3D11;
|
||||
using WinRT;
|
||||
using static Snap.Hutao.Win32.ConstValues;
|
||||
using static Snap.Hutao.Win32.D3D11;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IGameScreenCaptureService))]
|
||||
internal sealed partial class GameScreenCaptureService : IGameScreenCaptureService
|
||||
{
|
||||
private readonly ILogger<GameScreenCaptureService> logger;
|
||||
|
||||
public bool IsSupported()
|
||||
{
|
||||
if (!Core.UniversalApiContract.IsPresent(WindowsVersion.Windows10Version1903))
|
||||
{
|
||||
logger.LogWarning("Windows 10 Version 1903 or later is required for Windows.Graphics.Capture API.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GraphicsCaptureSession.IsSupported())
|
||||
{
|
||||
logger.LogWarning("GraphicsCaptureSession is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
public unsafe bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session)
|
||||
{
|
||||
session = default;
|
||||
|
||||
D3D11_CREATE_DEVICE_FLAG flag = D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT
|
||||
#if DEBUG
|
||||
| D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_DEBUG
|
||||
#endif
|
||||
;
|
||||
|
||||
HRESULT hr;
|
||||
hr = D3D11CreateDevice(default, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, default, flag, [], D3D11_SDK_VERSION, out ID3D11Device* pD3D11Device, out _, out _);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
logger.LogWarning("D3D11CreateDevice failed with code: {Code}", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = IUnknownMarshal.QueryInterface(pD3D11Device, in IDXGIDevice.IID, out IDXGIDevice* pDXGIDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
logger.LogWarning("ID3D11Device.QueryInterface<IDXGIDevice> failed with code: {Code}", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
IUnknownMarshal.Release(pDXGIDevice);
|
||||
|
||||
hr = CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out Win32.System.WinRT.IInspectable* inspectable);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
logger.LogWarning("CreateDirect3D11DeviceFromDXGIDevice failed with code: {Code}", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
IUnknownMarshal.Release(inspectable);
|
||||
|
||||
IDirect3DDevice direct3DDevice = IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
|
||||
|
||||
GameScreenCaptureContext captureContext = new(direct3DDevice, hwnd);
|
||||
session = new(captureContext, logger);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
using Snap.Hutao.Win32.Graphics.Dxgi;
|
||||
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
|
||||
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Graphics;
|
||||
using Windows.Graphics.Capture;
|
||||
using Windows.Graphics.DirectX.Direct3D11;
|
||||
using WinRT;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||
|
||||
internal sealed class GameScreenCaptureSession : IDisposable
|
||||
{
|
||||
private readonly GameScreenCaptureContext captureContext;
|
||||
private readonly Direct3D11CaptureFramePool framePool;
|
||||
private readonly GraphicsCaptureSession session;
|
||||
private readonly ILogger logger;
|
||||
|
||||
private TaskCompletionSource<IMemoryOwner<byte>>? frameRawPixelDataTaskCompletionSource;
|
||||
private bool isFrameRawPixelDataRequested;
|
||||
private SizeInt32 contentSize;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
public GameScreenCaptureSession(GameScreenCaptureContext captureContext, ILogger logger)
|
||||
{
|
||||
this.captureContext = captureContext;
|
||||
this.logger = logger;
|
||||
|
||||
contentSize = captureContext.Item.Size;
|
||||
|
||||
captureContext.Item.Closed += OnItemClosed;
|
||||
|
||||
framePool = captureContext.CreatePool();
|
||||
framePool.FrameArrived += OnFrameArrived;
|
||||
|
||||
session = captureContext.CreateSession(framePool);
|
||||
session.StartCapture();
|
||||
}
|
||||
|
||||
public async ValueTask<IMemoryOwner<byte>> RequestFrameRawPixelDataAsync()
|
||||
{
|
||||
if (Volatile.Read(ref isFrameRawPixelDataRequested))
|
||||
{
|
||||
HutaoException.InvalidOperation("The frame raw pixel data has already been requested.");
|
||||
}
|
||||
|
||||
if (isDisposed)
|
||||
{
|
||||
HutaoException.InvalidOperation("The session has been disposed.");
|
||||
}
|
||||
|
||||
frameRawPixelDataTaskCompletionSource = new();
|
||||
Volatile.Write(ref isFrameRawPixelDataRequested, true);
|
||||
|
||||
return await frameRawPixelDataTaskCompletionSource.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
session.Dispose();
|
||||
framePool.Dispose();
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
private void OnItemClosed(GraphicsCaptureItem sender, object args)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private unsafe void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
|
||||
{
|
||||
// Simply ignore the frame if the frame raw pixel data is not requested.
|
||||
if (!Volatile.Read(ref isFrameRawPixelDataRequested))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (Direct3D11CaptureFrame? frame = sender.TryGetNextFrame())
|
||||
{
|
||||
if (frame is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsReset = false;
|
||||
|
||||
if (frame.ContentSize != contentSize)
|
||||
{
|
||||
needsReset = true;
|
||||
contentSize = frame.ContentSize;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
UnsafeProcessFrameSurface(frame.Surface);
|
||||
}
|
||||
catch (Exception ex) // TODO: test if it's device lost.
|
||||
{
|
||||
logger.LogError(ex, "Failed to process the frame surface.");
|
||||
needsReset = true;
|
||||
}
|
||||
|
||||
if (needsReset)
|
||||
{
|
||||
captureContext.RecreatePool(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void UnsafeProcessFrameSurface(IDirect3DSurface surface)
|
||||
{
|
||||
IDirect3DDxgiInterfaceAccess access = surface.As<IDirect3DDxgiInterfaceAccess>();
|
||||
if (FAILED(access.GetInterface(in IDXGISurface.IID, out IDXGISurface* pDXGISurface)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(pDXGISurface->GetDesc(out DXGI_SURFACE_DESC dxgiSurfaceDesc)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Should be the same device used to create the frame pool.
|
||||
if (FAILED(pDXGISurface->GetDevice(in ID3D11Device.IID, out ID3D11Device* pD3D11Device)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC d3d11Texture2DDesc = default;
|
||||
d3d11Texture2DDesc.Width = dxgiSurfaceDesc.Width;
|
||||
d3d11Texture2DDesc.Height = dxgiSurfaceDesc.Height;
|
||||
d3d11Texture2DDesc.ArraySize = 1;
|
||||
|
||||
// We have to copy out the resource to a CPU readable texture.
|
||||
d3d11Texture2DDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ;
|
||||
|
||||
// DirectX will automatically convert any format to B8G8R8A8_UNORM.
|
||||
d3d11Texture2DDesc.Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
d3d11Texture2DDesc.MipLevels = 1;
|
||||
d3d11Texture2DDesc.SampleDesc.Count = 1;
|
||||
d3d11Texture2DDesc.Usage = D3D11_USAGE.D3D11_USAGE_STAGING;
|
||||
|
||||
if (FAILED(pD3D11Device->CreateTexture2D(ref d3d11Texture2DDesc, ref Unsafe.NullRef<D3D11_SUBRESOURCE_DATA>(), out ID3D11Texture2D* pD3D11Texture2D)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(access.GetInterface(in ID3D11Resource.IID, out ID3D11Resource* pD3D11Resource)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pD3D11Device->GetImmediateContext(out ID3D11DeviceContext* pD3D11DeviceContext);
|
||||
pD3D11DeviceContext->CopyResource((ID3D11Resource*)pD3D11Texture2D, pD3D11Resource);
|
||||
|
||||
if (FAILED(pD3D11DeviceContext->Map((ID3D11Resource*)pD3D11Texture2D, 0U, D3D11_MAP.D3D11_MAP_READ, 0U, out D3D11_MAPPED_SUBRESOURCE d3d11MappedSubresource)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The D3D11_MAPPED_SUBRESOURCE data is arranged as follows:
|
||||
// |--------- Row pitch ----------|
|
||||
// |---- Data width ----|- Blank -|
|
||||
// ┌────────────────────┬─────────┐
|
||||
// │ │ │
|
||||
// │ Actual data │ Stride │
|
||||
// │ │ │
|
||||
// └────────────────────┴─────────┘
|
||||
ReadOnlySpan2D<byte> subresource = new(d3d11MappedSubresource.pData, (int)d3d11Texture2DDesc.Height, (int)d3d11MappedSubresource.RowPitch);
|
||||
|
||||
int rowLength = contentSize.Width * 4;
|
||||
IMemoryOwner<byte> buffer = GameScreenCaptureMemoryPool.Shared.Rent(contentSize.Height * rowLength);
|
||||
|
||||
for (int row = 0; row < contentSize.Height; row++)
|
||||
{
|
||||
subresource[row][..rowLength].CopyTo(buffer.Memory.Span.Slice(row * rowLength, rowLength));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(frameRawPixelDataTaskCompletionSource);
|
||||
frameRawPixelDataTaskCompletionSource.SetResult(buffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||
|
||||
internal interface IGameScreenCaptureService
|
||||
{
|
||||
bool IsSupported();
|
||||
|
||||
bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
internal interface IMetadataListChapterSource
|
||||
{
|
||||
public List<Chapter> Chapters { get; set; }
|
||||
}
|
||||
@@ -23,6 +23,11 @@ internal static class MetadataServiceContextExtension
|
||||
listAchievementSource.Achievements = await metadataService.GetAchievementListAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataListChapterSource listChapterSource)
|
||||
{
|
||||
listChapterSource.Chapters = await metadataService.GetChapterListAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataListGachaEventSource listGachaEventSource)
|
||||
{
|
||||
listGachaEventSource.GachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ internal static class MetadataFileNames
|
||||
public const string FileNameAvatar = "Avatar";
|
||||
public const string FileNameAvatarCurve = "AvatarCurve";
|
||||
public const string FileNameAvatarPromote = "AvatarPromote";
|
||||
public const string FileNameChapter = "Chapter";
|
||||
public const string FileNameDisplayItem = "DisplayItem";
|
||||
public const string FileNameGachaEvent = "GachaEvent";
|
||||
public const string FileNameMaterial = "Material";
|
||||
|
||||
@@ -20,6 +20,11 @@ internal static class MetadataServiceListExtension
|
||||
return metadataService.FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<Chapter>> GetChapterListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<Chapter>>(FileNameChapter, token);
|
||||
}
|
||||
|
||||
public static ValueTask<List<AchievementGoal>> GetAchievementGoalListAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
|
||||
|
||||
@@ -323,8 +323,8 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240428000" />
|
||||
<PackageReference Include="QRCoder" Version="1.5.1" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -343,7 +343,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.10.1" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.11.0" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<NavigationView
|
||||
x:Name="NavView"
|
||||
Margin="-1,0,0,-1"
|
||||
Margin="0,0,0,0"
|
||||
shch:NavigationViewHelper.PaneCornerRadius="0"
|
||||
CompactPaneLength="48"
|
||||
IsBackEnabled="{x:Bind ContentFrame.CanGoBack, Mode=OneWay}"
|
||||
|
||||
@@ -41,9 +41,11 @@
|
||||
x:Name="ImageZoomBorder"
|
||||
VerticalAlignment="Top"
|
||||
cw:VisualExtensions.NormalizedCenterPoint="0.5">
|
||||
<cww:ConstrainedBox AspectRatio="1080:390" CornerRadius="{ThemeResource ControlCornerRadiusTop}">
|
||||
<cww:ConstrainedBox
|
||||
Margin="-4"
|
||||
AspectRatio="1080:390"
|
||||
CornerRadius="{ThemeResource ControlCornerRadiusTop}">
|
||||
<shci:CachedImage
|
||||
Margin="-1"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderMargin="16"
|
||||
PlaceholderSource="{StaticResource UI_EmotionIcon271}"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cw="using:CommunityToolkit.WinUI"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:cwconv="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
@@ -12,6 +13,7 @@
|
||||
xmlns:shch="using:Snap.Hutao.Control.Helper"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shme="using:Snap.Hutao.Model.Entity"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shvcp="using:Snap.Hutao.View.Card.Primitive"
|
||||
xmlns:shvd="using:Snap.Hutao.ViewModel.DailyNote"
|
||||
@@ -26,6 +28,16 @@
|
||||
<Page.Resources>
|
||||
<shc:BindingProxy x:Key="ViewModelBindingProxy" DataContext="{Binding}"/>
|
||||
|
||||
<cwconv:BoolToObjectConverter
|
||||
x:Name="DailyTaskIconConverter"
|
||||
FalseValue="{StaticResource UI_MarkQuest_Events_Start}"
|
||||
TrueValue="{StaticResource UI_MarkQuest_Events_Proce}"/>
|
||||
|
||||
<cwconv:BoolToObjectConverter
|
||||
x:Name="ArchonQuestIconConverter"
|
||||
FalseValue="{StaticResource UI_MarkQuest_Main_Start}"
|
||||
TrueValue="{StaticResource UI_MarkQuest_Main_Proce}"/>
|
||||
|
||||
<DataTemplate x:Key="UserAndUidTemplate">
|
||||
<Grid Padding="0,0,0,16">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Uid}"/>
|
||||
@@ -43,7 +55,7 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="DailyNoteEntryTemplate">
|
||||
<DataTemplate x:Key="DailyNoteEntryTemplate" x:DataType="shme:DailyNoteEntry">
|
||||
<ItemContainer cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<ItemContainer.Resources>
|
||||
<SolidColorBrush x:Key="ItemContainerPointerOverBackground" Color="Transparent"/>
|
||||
@@ -108,6 +120,43 @@
|
||||
Grid.Row="1"
|
||||
Margin="0,8,0,0"
|
||||
Spacing="6">
|
||||
<Grid Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar
|
||||
Grid.ColumnSpan="2"
|
||||
Height="40"
|
||||
MinHeight="48"
|
||||
Background="{x:Null}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Maximum="{Binding ArchonQuestView.Ids.Count, Mode=OneWay}"
|
||||
Opacity="{StaticResource LargeBackgroundProgressBarOpacity}"
|
||||
Value="{Binding ArchonQuestView.ProgressValue, Mode=OneWay}"/>
|
||||
<shci:CachedImage
|
||||
Grid.Column="0"
|
||||
Margin="4"
|
||||
VerticalAlignment="Center"
|
||||
shch:FrameworkElementHelper.SquareLength="32"
|
||||
Source="{Binding DailyNote.IsArchonQuestFinished, Converter={StaticResource ArchonQuestIconConverter}}"/>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding ArchonQuestView.ProgressFormatted, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding ArchonQuestView.ChapterFormatted, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
@@ -202,7 +251,7 @@
|
||||
Margin="4"
|
||||
VerticalAlignment="Center"
|
||||
shch:FrameworkElementHelper.SquareLength="32"
|
||||
Source="{StaticResource UI_MarkQuest_Events_Proce}"/>
|
||||
Source="{Binding DailyNote.DailyTask.IsExtraTaskRewardReceived, Converter={StaticResource DailyTaskIconConverter}}"/>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
@@ -333,7 +382,7 @@
|
||||
ItemsSource="{Binding DailyNote.Expeditions, Mode=OneWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cwc:UniformGrid
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="8"
|
||||
Columns="2"
|
||||
RowSpacing="8"/>
|
||||
@@ -450,16 +499,16 @@
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<cwc:HeaderedContentControl
|
||||
<cwcont:HeaderedContentControl
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsEnabled="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolNegationConverter}}">
|
||||
<cwc:HeaderedContentControl.Header>
|
||||
<cwcont:HeaderedContentControl.Header>
|
||||
<TextBlock
|
||||
Margin="1,0,0,5"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageDailyNoteSettingRefreshHeader}"/>
|
||||
</cwc:HeaderedContentControl.Header>
|
||||
</cwcont:HeaderedContentControl.Header>
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<InfoBar
|
||||
Title="{shcm:ResourceString Name=ViewPageDailyNoteSettingRefreshElevatedHint}"
|
||||
@@ -467,12 +516,12 @@
|
||||
IsOpen="True"
|
||||
Severity="Warning"
|
||||
Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<cwc:SettingsCard
|
||||
<cwcont:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageDailyNoteSettingAutoRefreshDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageDailyNoteSettingAutoRefresh}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Margin="24,0,0,0" IsOn="{Binding DailyNoteOptions.IsAutoRefreshEnabled, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
</cwcont:SettingsCard>
|
||||
<RadioButtons
|
||||
Margin="1,11,0,5"
|
||||
IsEnabled="{Binding DailyNoteOptions.IsAutoRefreshEnabled}"
|
||||
@@ -488,15 +537,15 @@
|
||||
</RadioButtons.ItemTemplate>
|
||||
</RadioButtons>
|
||||
</StackPanel>
|
||||
</cwc:HeaderedContentControl>
|
||||
</cwcont:HeaderedContentControl>
|
||||
|
||||
<cwc:HeaderedContentControl
|
||||
<cwcont:HeaderedContentControl
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsEnabled="{Binding RuntimeOptions.IsToastAvailable}">
|
||||
<cwc:HeaderedContentControl.Header>
|
||||
<cwcont:HeaderedContentControl.Header>
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageDailyNoteNotificationHeader}"/>
|
||||
</cwc:HeaderedContentControl.Header>
|
||||
</cwcont:HeaderedContentControl.Header>
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<InfoBar
|
||||
Title="胡桃的通知权限已被关闭"
|
||||
@@ -504,23 +553,23 @@
|
||||
IsOpen="True"
|
||||
Severity="Warning"
|
||||
Visibility="{Binding RuntimeOptions.IsToastAvailable, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<cwc:SettingsCard
|
||||
<cwcont:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageDailyNoteSlientModeDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageDailyNoteSlientModeHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Margin="24,0,0,0" IsOn="{Binding DailyNoteOptions.IsSilentWhenPlayingGame, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard
|
||||
</cwcont:SettingsCard>
|
||||
<cwcont:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageDailyNoteReminderDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageDailyNoteReminderHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Margin="24,0,0,0" IsOn="{Binding DailyNoteOptions.IsReminderNotification, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
</cwcont:SettingsCard>
|
||||
</StackPanel>
|
||||
</cwc:HeaderedContentControl>
|
||||
</cwcont:HeaderedContentControl>
|
||||
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageDailyNoteDataInteropHeader}"/>
|
||||
<cwc:SettingsCard
|
||||
<cwcont:SettingsCard
|
||||
Command="{Binding ConfigDailyNoteWebhookUrlCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewPageDailyNoteConfigWebhookDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageDailyNoteConfigWebhookHeader}"
|
||||
|
||||
@@ -46,7 +46,7 @@ internal sealed class CultivateItemView : ObservableObject, IEntityWithMetadata<
|
||||
/// <summary>
|
||||
/// 是否为今日物品
|
||||
/// </summary>
|
||||
public bool IsToday { get => Inner.IsTodaysItem(); }
|
||||
public bool IsToday { get => Inner.IsTodaysItem(true); }
|
||||
|
||||
/// <summary>
|
||||
/// 星期中的日期
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.DailyNote;
|
||||
|
||||
internal sealed class DailyNoteArchonQuestView
|
||||
{
|
||||
private readonly WebDailyNote? dailyNote;
|
||||
|
||||
private DailyNoteArchonQuestView(WebDailyNote? dailyNote, List<Chapter> chapters)
|
||||
{
|
||||
this.dailyNote = dailyNote;
|
||||
Ids = chapters
|
||||
.Where(chapter => chapter.QuestType is QuestType.AQ)
|
||||
.Select(chapter => chapter.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<ChapterId> Ids { get; set; } = default!;
|
||||
|
||||
public int ProgressValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetFirstArchonQuest(out ArchonQuest? quest))
|
||||
{
|
||||
return Ids.IndexOf(quest.Id);
|
||||
}
|
||||
|
||||
return Ids.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public string ProgressFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetFirstArchonQuest(out ArchonQuest? quest))
|
||||
{
|
||||
return quest.Status.GetLocalizedDescription();
|
||||
}
|
||||
|
||||
return SH.WebDailyNoteArchonQuestStatusFinished;
|
||||
}
|
||||
}
|
||||
|
||||
public string ChapterFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetFirstArchonQuest(out ArchonQuest? quest))
|
||||
{
|
||||
return $"{quest.ChapterNum} {quest.ChapterTitle}";
|
||||
}
|
||||
|
||||
return SH.WebDailyNoteArchonQuestChapterFinished;
|
||||
}
|
||||
}
|
||||
|
||||
public static DailyNoteArchonQuestView Create(WebDailyNote? dailyNote, List<Chapter> chapters)
|
||||
{
|
||||
return new(dailyNote, chapters);
|
||||
}
|
||||
|
||||
private bool TryGetFirstArchonQuest([NotNullWhen(true)] out ArchonQuest? archonQuest)
|
||||
{
|
||||
if (dailyNote is { ArchonQuestProgress.List: [{ } target, ..] })
|
||||
{
|
||||
archonQuest = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
archonQuest = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Control;
|
||||
@@ -27,6 +28,7 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IDailyNoteService dailyNoteService;
|
||||
private readonly DailyNoteOptions dailyNoteOptions;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -53,22 +55,26 @@ internal sealed partial class DailyNoteViewModel : Abstraction.ViewModel
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
try
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<UserAndUid> roles = await userService.GetRoleCollectionAsync().ConfigureAwait(false);
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService.GetDailyNoteEntryCollectionAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<UserAndUid> roles = await userService.GetRoleCollectionAsync().ConfigureAwait(false);
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService.GetDailyNoteEntryCollectionAsync().ConfigureAwait(false);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UserAndUids = roles;
|
||||
DailyNoteEntries = entries;
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return false;
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
UserAndUids = roles;
|
||||
DailyNoteEntries = entries;
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[Command("TrackRoleCommand")]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
@@ -17,6 +18,7 @@ internal sealed partial class DailyNoteViewModelSlim : Abstraction.ViewModelSlim
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IDailyNoteService dailyNoteService;
|
||||
|
||||
private List<DailyNoteEntry>? dailyNoteEntries;
|
||||
@@ -29,25 +31,28 @@ internal sealed partial class DailyNoteViewModelSlim : Abstraction.ViewModelSlim
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
try
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService
|
||||
.GetDailyNoteEntryCollectionAsync()
|
||||
.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<DailyNoteEntry> entries = await dailyNoteService
|
||||
.GetDailyNoteEntryCollectionAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// 此处使用浅拷贝的列表以避免当导航到实时便笺页面后
|
||||
// 由于主页尚未卸载,添加或删除便笺可能会崩溃的问题
|
||||
List<DailyNoteEntry> entryList = [.. entries];
|
||||
// 此处使用浅拷贝的列表以避免当导航到实时便笺页面后
|
||||
// 由于主页尚未卸载,添加或删除便笺可能会崩溃的问题
|
||||
List<DailyNoteEntry> entryList = [.. entries];
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
DailyNoteEntries = entryList;
|
||||
IsInitialized = true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return;
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
DailyNoteEntries = entryList;
|
||||
IsInitialized = true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ internal static class StaticResource
|
||||
{ "IconElement", 0 },
|
||||
{ "ItemIcon", 0 },
|
||||
{ "LoadingPic", 0 },
|
||||
{ "Mark", 0 },
|
||||
{ "MonsterIcon", 0 },
|
||||
{ "MonsterSmallIcon", 0 },
|
||||
{ "NameCardIcon", 0 },
|
||||
@@ -62,6 +63,7 @@ internal static class StaticResource
|
||||
{ "IconElement", 3 },
|
||||
{ "ItemIcon", 4 },
|
||||
{ "LoadingPic", 2 },
|
||||
{ "Mark", 0 },
|
||||
{ "MonsterIcon", 3 },
|
||||
{ "MonsterSmallIcon", 2 },
|
||||
{ "NameCardIcon", 3 },
|
||||
|
||||
@@ -14,8 +14,8 @@ internal static class StaticResourceHttpHeaderBuilderExtension
|
||||
where TBuilder : IHttpHeadersBuilder<HttpHeaders>
|
||||
{
|
||||
return builder
|
||||
.SetHeader("x-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
|
||||
.SetHeader("x-archive", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full)}");
|
||||
.SetHeader("x-hutao-quality", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageQuality, StaticResourceQuality.Raw)}")
|
||||
.SetHeader("x-hutao-archive", $"{UnsafeLocalSetting.Get(SettingKeys.StaticResourceImageArchive, StaticResourceArchive.Full)}");
|
||||
}
|
||||
|
||||
public static TBuilder SetStaticResourceControlHeadersIf<TBuilder>(this TBuilder builder, bool condition)
|
||||
|
||||
@@ -13,13 +13,19 @@ using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.Graphics.Direct3D;
|
||||
using Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
using Snap.Hutao.Win32.Graphics.Dxgi;
|
||||
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Snap.Hutao.Win32.System.WinRT;
|
||||
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
using Windows.Graphics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Capture;
|
||||
using Windows.Graphics.DirectX;
|
||||
using Windows.Graphics.DirectX.Direct3D11;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using WinRT;
|
||||
using static Snap.Hutao.Win32.ConstValues;
|
||||
using static Snap.Hutao.Win32.D3D11;
|
||||
using static Snap.Hutao.Win32.Macros;
|
||||
@@ -42,8 +48,6 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly MainWindow mainWindow;
|
||||
|
||||
private long counter;
|
||||
|
||||
private UploadAnnouncement announcement = new();
|
||||
|
||||
public UploadAnnouncement Announcement { get => announcement; set => SetProperty(ref announcement, value); }
|
||||
@@ -159,10 +163,8 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
[Command("TestWindowsGraphicsCaptureCommand")]
|
||||
private unsafe void TestWindowsGraphicsCapture()
|
||||
{
|
||||
counter = 0;
|
||||
|
||||
// https://github.com/obsproject/obs-studio/blob/master/libobs-winrt/winrt-capture.cpp
|
||||
if (!UniversalApiContract.IsPresent(WindowsVersion.Windows10Version1903))
|
||||
if (!Core.UniversalApiContract.IsPresent(WindowsVersion.Windows10Version1903))
|
||||
{
|
||||
logger.LogWarning("Windows 10 Version 1903 or later is required for Windows.Graphics.Capture API.");
|
||||
return;
|
||||
@@ -180,21 +182,100 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
{
|
||||
if (SUCCEEDED(IUnknownMarshal.QueryInterface(pD3D11Device, in IDXGIDevice.IID, out IDXGIDevice* pDXGIDevice)))
|
||||
{
|
||||
if (SUCCEEDED(CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out IInspectable* inspectable)))
|
||||
if (SUCCEEDED(CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out Win32.System.WinRT.IInspectable* inspectable)))
|
||||
{
|
||||
IDirect3DDevice direct3DDevice = WinRT.CastExtensions.As<IDirect3DDevice>(WinRT.IInspectable.FromAbi((nint)inspectable));
|
||||
IDirect3DDevice direct3DDevice = WinRT.IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
|
||||
|
||||
SizeInt32 size = new(1920, 1080);
|
||||
using (Direct3D11CaptureFramePool framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, size))
|
||||
HWND hwnd = serviceProvider.GetRequiredService<ICurrentWindowReference>().GetWindowHandle();
|
||||
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out GraphicsCaptureItem item);
|
||||
|
||||
using (Direct3D11CaptureFramePool framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, item.Size))
|
||||
{
|
||||
framePool.FrameArrived += (pool, obj) =>
|
||||
framePool.FrameArrived += (pool, _) =>
|
||||
{
|
||||
Interlocked.Increment(ref counter);
|
||||
using (Direct3D11CaptureFrame frame = framePool.TryGetNextFrame())
|
||||
using (Direct3D11CaptureFrame frame = pool.TryGetNextFrame())
|
||||
{
|
||||
if (frame is not null)
|
||||
{
|
||||
logger.LogInformation("Content Size: {Width} x {Height} {Count}", frame.ContentSize.Width, frame.ContentSize.Height, Volatile.Read(ref counter));
|
||||
logger.LogInformation("Content Size: {Width} x {Height}", frame.ContentSize.Width, frame.ContentSize.Height);
|
||||
|
||||
IDirect3DDxgiInterfaceAccess access = frame.Surface.As<IDirect3DDxgiInterfaceAccess>();
|
||||
if (FAILED(access.GetInterface(in IDXGISurface.IID, out IDXGISurface* pDXGISurface)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(pDXGISurface->GetDesc(out DXGI_SURFACE_DESC surfaceDesc)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC texture2DDesc = default;
|
||||
texture2DDesc.Width = surfaceDesc.Width;
|
||||
texture2DDesc.Height = surfaceDesc.Height;
|
||||
texture2DDesc.ArraySize = 1;
|
||||
texture2DDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ;
|
||||
texture2DDesc.Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
texture2DDesc.MipLevels = 1;
|
||||
texture2DDesc.SampleDesc.Count = 1;
|
||||
texture2DDesc.Usage = D3D11_USAGE.D3D11_USAGE_STAGING;
|
||||
|
||||
if (FAILED(pDXGISurface->GetDevice(in ID3D11Device.IID, out ID3D11Device* pD3D11Device)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(pD3D11Device->CreateTexture2D(ref texture2DDesc, ref Unsafe.NullRef<D3D11_SUBRESOURCE_DATA>(), out ID3D11Texture2D* pTexture2D)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(access.GetInterface(in ID3D11Resource.IID, out ID3D11Resource* pD3D11Resource)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pD3D11Device->GetImmediateContext(out ID3D11DeviceContext* pDeviceContext);
|
||||
pDeviceContext->CopyResource((ID3D11Resource*)pTexture2D, pD3D11Resource);
|
||||
|
||||
if (FAILED(pDeviceContext->Map((ID3D11Resource*)pTexture2D, 0, D3D11_MAP.D3D11_MAP_READ, 0, out D3D11_MAPPED_SUBRESOURCE mappedSubresource)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int size = (int)(mappedSubresource.RowPitch * texture2DDesc.Height * 4);
|
||||
|
||||
SoftwareBitmap softwareBitmap = new(BitmapPixelFormat.Bgra8, (int)texture2DDesc.Width, (int)texture2DDesc.Height, BitmapAlphaMode.Premultiplied);
|
||||
using (BitmapBuffer bitmapBuffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
|
||||
{
|
||||
using (IMemoryBufferReference reference = bitmapBuffer.CreateReference())
|
||||
{
|
||||
reference.As<IMemoryBufferByteAccess>().GetBuffer(out Span<byte> bufferSpan);
|
||||
fixed (byte* p = bufferSpan)
|
||||
{
|
||||
for (uint i = 0; i < texture2DDesc.Height; i++)
|
||||
{
|
||||
System.Buffer.MemoryCopy(((byte*)mappedSubresource.pData) + (i * mappedSubresource.RowPitch), p + (i * texture2DDesc.Width * 4), texture2DDesc.Width * 4, texture2DDesc.Width * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (InMemoryRandomAccessStream stream = new())
|
||||
{
|
||||
BitmapEncoder encoder = BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream).AsTask().Result;
|
||||
encoder.SetSoftwareBitmap(softwareBitmap);
|
||||
encoder.FlushAsync().AsTask().Wait();
|
||||
|
||||
using (FileStream fileStream = new("D:\\test.png", FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
|
||||
{
|
||||
stream.AsStream().CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
_ = 1;
|
||||
|
||||
pDeviceContext->Unmap((ID3D11Resource*)pTexture2D, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -203,9 +284,6 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
||||
}
|
||||
};
|
||||
|
||||
HWND hwnd = serviceProvider.GetRequiredService<ICurrentWindowReference>().GetWindowHandle();
|
||||
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out GraphicsCaptureItem item);
|
||||
|
||||
using (GraphicsCaptureSession captureSession = framePool.CreateCaptureSession(item))
|
||||
{
|
||||
captureSession.IsCursorCaptureEnabled = false;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.DataSigning;
|
||||
|
||||
internal static class DataSignAlgorithm
|
||||
@@ -19,7 +21,7 @@ internal static class DataSignAlgorithm
|
||||
}
|
||||
|
||||
#pragma warning disable CA1308
|
||||
string check = Core.Convert.ToMd5HexString(dsContent).ToLowerInvariant();
|
||||
string check = Hash.MD5HexString(dsContent).ToLowerInvariant();
|
||||
#pragma warning restore CA1308
|
||||
|
||||
return $"{t},{r},{check}";
|
||||
|
||||
@@ -132,4 +132,10 @@ internal sealed class DailyNote : DailyNoteCommon
|
||||
|
||||
[JsonPropertyName("archon_quest_progress")]
|
||||
public ArchonQuestProgress ArchonQuestProgress { get; set; } = default!;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsArchonQuestFinished
|
||||
{
|
||||
get => ArchonQuestProgress.List.Count == 0;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ internal sealed partial class HutaoInfrastructureClient
|
||||
{
|
||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||
.SetRequestUri(HutaoEndpoints.PatchSnapHutao)
|
||||
.SetHeader("x-device-id", runtimeOptions.DeviceId)
|
||||
.SetHeader("x-hutao-device-id", runtimeOptions.DeviceId)
|
||||
.Get();
|
||||
|
||||
HutaoResponse<HutaoVersionInformation>? resp = await builder.TryCatchSendAsync<HutaoResponse<HutaoVersionInformation>>(httpClient, logger, token).ConfigureAwait(false);
|
||||
|
||||
@@ -15,12 +15,17 @@ internal readonly partial struct HRESULT
|
||||
public static unsafe implicit operator int(HRESULT value) => *(int*)&value;
|
||||
|
||||
public static unsafe implicit operator HRESULT(int value) => *(HRESULT*)&value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"0x{Value:X8}";
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[DebuggerDisplay("{DebuggerDisplay}")]
|
||||
internal readonly partial struct HRESULT
|
||||
{
|
||||
private string DebuggerDisplay => $"0x{Value:X8}";
|
||||
private string DebuggerDisplay { get => ToString(); }
|
||||
}
|
||||
#endif
|
||||
@@ -9,8 +9,6 @@ internal readonly struct HWND
|
||||
|
||||
public HWND(nint value) => Value = value;
|
||||
|
||||
public bool IsNull => Value is 0;
|
||||
|
||||
public static unsafe implicit operator HWND(nint value) => *(HWND*)&value;
|
||||
|
||||
public static unsafe implicit operator nint(HWND value) => *(nint*)&value;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
|
||||
[SuppressMessage("", "SA1307")]
|
||||
internal struct D3D11_MAPPED_SUBRESOURCE
|
||||
{
|
||||
public unsafe void* pData;
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Runtime.Versioning;
|
||||
namespace Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
|
||||
[SupportedOSPlatform("windows6.1")]
|
||||
internal unsafe readonly struct ID3D11Device
|
||||
internal unsafe struct ID3D11Device
|
||||
{
|
||||
public readonly Vftbl* ThisPtr;
|
||||
|
||||
@@ -25,6 +25,28 @@ internal unsafe readonly struct ID3D11Device
|
||||
}
|
||||
}
|
||||
|
||||
public HRESULT CreateTexture2D(ref readonly D3D11_TEXTURE2D_DESC desc, [AllowNull] ref readonly D3D11_SUBRESOURCE_DATA initialData, [MaybeNull] out ID3D11Texture2D* pTexture2D)
|
||||
{
|
||||
fixed (D3D11_TEXTURE2D_DESC* pDesc = &desc)
|
||||
{
|
||||
fixed (D3D11_SUBRESOURCE_DATA* pInitialData = &initialData)
|
||||
{
|
||||
fixed (ID3D11Texture2D** ppTexture2D = &pTexture2D)
|
||||
{
|
||||
return ThisPtr->CreateTexture2D((ID3D11Device*)Unsafe.AsPointer(ref this), pDesc, pInitialData, ppTexture2D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GetImmediateContext(out ID3D11DeviceContext* pImmediateContext)
|
||||
{
|
||||
fixed (ID3D11DeviceContext** ppImmediateContext = &pImmediateContext)
|
||||
{
|
||||
ThisPtr->GetImmediateContext((ID3D11Device*)Unsafe.AsPointer(ref this), ppImmediateContext);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct Vftbl
|
||||
{
|
||||
internal readonly IUnknown.Vftbl IUnknownVftbl;
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Runtime.Versioning;
|
||||
namespace Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||
|
||||
[SupportedOSPlatform("windows6.1")]
|
||||
internal unsafe readonly struct ID3D11DeviceContext
|
||||
internal unsafe struct ID3D11DeviceContext
|
||||
{
|
||||
public readonly Vftbl* ThisPtr;
|
||||
|
||||
@@ -24,6 +24,26 @@ internal unsafe readonly struct ID3D11DeviceContext
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1313")]
|
||||
public HRESULT Map(ID3D11Resource* pResource, uint Subresource, D3D11_MAP MapType, uint MapFlags, [MaybeNull] out D3D11_MAPPED_SUBRESOURCE mappedResource)
|
||||
{
|
||||
fixed (D3D11_MAPPED_SUBRESOURCE* pMappedResource = &mappedResource)
|
||||
{
|
||||
return ThisPtr->Map((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pResource, Subresource, MapType, MapFlags, pMappedResource);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SA1313")]
|
||||
public void Unmap(ID3D11Resource* pResource, uint Subresource)
|
||||
{
|
||||
ThisPtr->Unmap((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pResource, Subresource);
|
||||
}
|
||||
|
||||
public void CopyResource(ID3D11Resource* pDstResource, ID3D11Resource* pSrcResource)
|
||||
{
|
||||
ThisPtr->CopyResource((ID3D11DeviceContext*)Unsafe.AsPointer(ref this), pDstResource, pSrcResource);
|
||||
}
|
||||
|
||||
internal readonly struct Vftbl
|
||||
{
|
||||
internal readonly ID3D11DeviceChild.Vftbl ID3D11DeviceChildVftbl;
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Win32.Graphics.Dxgi;
|
||||
|
||||
internal unsafe readonly struct IDXGISurface
|
||||
internal unsafe struct IDXGISurface
|
||||
{
|
||||
public readonly Vftbl* ThisPtr;
|
||||
|
||||
@@ -20,6 +20,26 @@ internal unsafe readonly struct IDXGISurface
|
||||
}
|
||||
}
|
||||
|
||||
public HRESULT GetDevice<T>(ref readonly Guid iid, out T* pDevice)
|
||||
where T : unmanaged
|
||||
{
|
||||
fixed (Guid* riid = &iid)
|
||||
{
|
||||
fixed (T** ppDevice = &pDevice)
|
||||
{
|
||||
return ThisPtr->IDXGIDeviceSubObjectVftbl.GetDevice((IDXGIDeviceSubObject*)Unsafe.AsPointer(ref this), riid, (void**)ppDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HRESULT GetDesc(out DXGI_SURFACE_DESC desc)
|
||||
{
|
||||
fixed (DXGI_SURFACE_DESC* pDesc = &desc)
|
||||
{
|
||||
return ThisPtr->GetDesc((IDXGISurface*)Unsafe.AsPointer(ref this), pDesc);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct Vftbl
|
||||
{
|
||||
internal readonly IDXGIDeviceSubObject.Vftbl IDXGIDeviceSubObjectVftbl;
|
||||
|
||||
@@ -5,5 +5,13 @@ namespace Snap.Hutao.Win32.Graphics.Gdi;
|
||||
|
||||
internal readonly struct HDC
|
||||
{
|
||||
public static readonly HDC NULL = 0;
|
||||
|
||||
public readonly nint Value;
|
||||
|
||||
public static unsafe implicit operator HDC(nint value) => *(HDC*)&value;
|
||||
|
||||
public static unsafe bool operator ==(HDC left, HDC right) => *(nint*)&left == *(nint*)&right;
|
||||
|
||||
public static unsafe bool operator !=(HDC left, HDC right) => *(nint*)&left != *(nint*)&right;
|
||||
}
|
||||
@@ -7,14 +7,14 @@ namespace Snap.Hutao.Win32.System.Com;
|
||||
|
||||
internal static class IUnknownMarshal
|
||||
{
|
||||
public static unsafe HRESULT QueryInterface<TInterface>(void* pIUnknown, ref readonly Guid riid, out TInterface* pvObject)
|
||||
public static unsafe HRESULT QueryInterface<TInterface>(void* pIUnknown, ref readonly Guid iid, out TInterface* pvObject)
|
||||
where TInterface : unmanaged
|
||||
{
|
||||
fixed (Guid* riid2 = &riid)
|
||||
fixed (Guid* riid = &iid)
|
||||
{
|
||||
fixed (TInterface** ppvObject = &pvObject)
|
||||
{
|
||||
return ((IUnknown*)pIUnknown)->ThisPtr->QueryInterface((IUnknown*)pIUnknown, riid2, (void**)ppvObject);
|
||||
return ((IUnknown*)pIUnknown)->ThisPtr->QueryInterface((IUnknown*)pIUnknown, riid, (void**)ppvObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
|
||||
[ComImport]
|
||||
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IDirect3DDxgiInterfaceAccess
|
||||
{
|
||||
[PreserveSig]
|
||||
unsafe HRESULT GetInterface(Guid* iid, void** p);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
|
||||
internal static class IDirect3DDxgiInterfaceAccessExtension
|
||||
{
|
||||
public static unsafe HRESULT GetInterface<T>(this IDirect3DDxgiInterfaceAccess access, ref readonly Guid iid, out T* p)
|
||||
where T : unmanaged
|
||||
{
|
||||
fixed (Guid* riid = &iid)
|
||||
{
|
||||
fixed (T** pp = &p)
|
||||
{
|
||||
return access.GetInterface(riid, (void**)pp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,11 @@
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.Graphics.Gdi;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Graphics.Capture;
|
||||
|
||||
namespace Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
||||
|
||||
internal static class GraphicsCaptureItemInteropExtension
|
||||
internal static class IGraphicsCaptureItemInteropExtension
|
||||
{
|
||||
public static unsafe HRESULT CreateForWindow(this IGraphicsCaptureItemInterop interop, HWND window, out GraphicsCaptureItem result)
|
||||
{
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Snap.Hutao.Win32.UI.Shell.Common;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Win32.Foundation;
|
||||
using Snap.Hutao.Win32.System.Com;
|
||||
using Snap.Hutao.Win32.System.Com.StructuredStorage;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
Reference in New Issue
Block a user