From 1f2d3a40d92fae1f63dca8daa2e1377ebafd5767 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Sat, 19 Oct 2024 15:20:58 +0800 Subject: [PATCH] Add HutaoInterop Pipe --- BetterGenshinImpact/App.xaml.cs | 8 +- .../BetterGenshinImpact.csproj | 1 + BetterGenshinImpact/Hutao/HutaoNamedPipe.cs | 43 ++++++++++ .../Hutao/PipePacketCommand.cs | 6 ++ .../Hutao/PipePacketContentType.cs | 7 ++ BetterGenshinImpact/Hutao/PipePacketHeader.cs | 26 ++++++ BetterGenshinImpact/Hutao/PipePacketType.cs | 9 ++ .../Hutao/PipeStreamExtension.cs | 83 +++++++++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 BetterGenshinImpact/Hutao/HutaoNamedPipe.cs create mode 100644 BetterGenshinImpact/Hutao/PipePacketCommand.cs create mode 100644 BetterGenshinImpact/Hutao/PipePacketContentType.cs create mode 100644 BetterGenshinImpact/Hutao/PipePacketHeader.cs create mode 100644 BetterGenshinImpact/Hutao/PipePacketType.cs create mode 100644 BetterGenshinImpact/Hutao/PipeStreamExtension.cs diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index b0812ecd..a6e4989a 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -1,6 +1,7 @@ using BetterGenshinImpact.GameTask; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Helpers.Extensions; +using BetterGenshinImpact.Hutao; using BetterGenshinImpact.Service; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.Service.Notification; @@ -10,6 +11,7 @@ using BetterGenshinImpact.View.Pages; using BetterGenshinImpact.ViewModel; using BetterGenshinImpact.ViewModel.Pages; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; @@ -36,6 +38,10 @@ public partial class App : Application .CheckIntegration() .UseElevated() .UseSingleInstance("BetterGI") + .ConfigureLogging(builder => + { + builder.ClearProviders(); + }) .ConfigureServices( (context, services) => { @@ -66,7 +72,6 @@ public partial class App : Application // App Host services.AddHostedService(); - // Page resolver service services.AddSingleton(); @@ -98,6 +103,7 @@ public partial class App : Application services.AddHostedService(sp => sp.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Configuration //services.Configure(context.Configuration.GetSection(nameof(AppConfig))); diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 54727d04..33891dc8 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -64,6 +64,7 @@ + diff --git a/BetterGenshinImpact/Hutao/HutaoNamedPipe.cs b/BetterGenshinImpact/Hutao/HutaoNamedPipe.cs new file mode 100644 index 00000000..87929f13 --- /dev/null +++ b/BetterGenshinImpact/Hutao/HutaoNamedPipe.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO.Pipes; +using System.Text; + +namespace BetterGenshinImpact.Hutao; + +internal sealed class HutaoNamedPipe : IDisposable +{ + private readonly NamedPipeClientStream clientStream = new(".", "Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough); + + private readonly IServiceProvider serviceProvider; + + private Lazy isSupported; + + public HutaoNamedPipe(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + + isSupported = new(() => + { + try + { + clientStream.Connect(TimeSpan.Zero); + return true; + } + catch (TimeoutException) + { + return false; + } + }); + } + + public bool IsSupported + { + get => isSupported.Value; + } + + public void Dispose() + { + clientStream.Dispose(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Hutao/PipePacketCommand.cs b/BetterGenshinImpact/Hutao/PipePacketCommand.cs new file mode 100644 index 00000000..2b1c5c44 --- /dev/null +++ b/BetterGenshinImpact/Hutao/PipePacketCommand.cs @@ -0,0 +1,6 @@ +namespace BetterGenshinImpact.Hutao; + +internal enum PipePacketCommand : byte +{ + None = 0, +} \ No newline at end of file diff --git a/BetterGenshinImpact/Hutao/PipePacketContentType.cs b/BetterGenshinImpact/Hutao/PipePacketContentType.cs new file mode 100644 index 00000000..63159149 --- /dev/null +++ b/BetterGenshinImpact/Hutao/PipePacketContentType.cs @@ -0,0 +1,7 @@ +namespace BetterGenshinImpact.Hutao; + +internal enum PipePacketContentType : byte +{ + None = 0, + Json = 1, +} \ No newline at end of file diff --git a/BetterGenshinImpact/Hutao/PipePacketHeader.cs b/BetterGenshinImpact/Hutao/PipePacketHeader.cs new file mode 100644 index 00000000..d3c518dc --- /dev/null +++ b/BetterGenshinImpact/Hutao/PipePacketHeader.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace BetterGenshinImpact.Hutao; + +// Layout: +// 0 1 2 3 4 Bytes +// ┌─────────┬──────┬─────────┬─────────────┐ +// │ Version │ Type │ Command │ ContentType │ +// ├─────────┴──────┴─────────┴─────────────┤ 4 Bytes +// │ ContentLength │ +// ├────────────────────────────────────────┤ 8 Bytes +// │ │ +// │─────────────── Checksum ───────────────│ +// │ │ +// └────────────────────────────────────────┘ 16 Bytes +// Any content will be placed after the header. +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct PipePacketHeader +{ + public byte Version; + public PipePacketType Type; + public PipePacketCommand Command; + public PipePacketContentType ContentType; + public int ContentLength; + public ulong Checksum; +} \ No newline at end of file diff --git a/BetterGenshinImpact/Hutao/PipePacketType.cs b/BetterGenshinImpact/Hutao/PipePacketType.cs new file mode 100644 index 00000000..610a456b --- /dev/null +++ b/BetterGenshinImpact/Hutao/PipePacketType.cs @@ -0,0 +1,9 @@ +namespace BetterGenshinImpact.Hutao; + +internal enum PipePacketType : byte +{ + None = 0, + Request = 1, + Response = 2, + SessionTermination = 3, +} \ No newline at end of file diff --git a/BetterGenshinImpact/Hutao/PipeStreamExtension.cs b/BetterGenshinImpact/Hutao/PipeStreamExtension.cs new file mode 100644 index 00000000..fab7b70d --- /dev/null +++ b/BetterGenshinImpact/Hutao/PipeStreamExtension.cs @@ -0,0 +1,83 @@ +using System; +using System.Buffers; +using System.IO.Hashing; +using System.IO.Pipes; +using System.Text.Json; + +namespace BetterGenshinImpact.Hutao; + +internal static class PipeStreamExtension +{ + public static TData? ReadJsonContent(this PipeStream stream, ref readonly PipePacketHeader header) + { + using (IMemoryOwner memoryOwner = MemoryPool.Shared.Rent(header.ContentLength)) + { + Span content = memoryOwner.Memory.Span[..header.ContentLength]; + stream.ReadExactly(content); + + if (XxHash64.HashToUInt64(content) != header.Checksum) + { + throw new InvalidOperationException("PipePacket Content Hash incorrect"); + } + return JsonSerializer.Deserialize(content); + } + } + + public static void ReadPacket(this PipeStream stream, out PipePacketHeader header, out TData? data) + where TData : class + { + data = default; + + stream.ReadPacket(out header); + if (header.ContentType is PipePacketContentType.Json) + { + data = stream.ReadJsonContent(in header); + } + } + + public static unsafe void ReadPacket(this PipeStream stream, out PipePacketHeader header) + { + fixed (PipePacketHeader* pHeader = &header) + { + stream.ReadExactly(new(pHeader, sizeof(PipePacketHeader))); + } + } + + public static void WritePacketWithJsonContent(this PipeStream stream, byte version, PipePacketType type, PipePacketCommand command, TData data) + { + PipePacketHeader header = default; + header.Version = version; + header.Type = type; + header.Command = command; + header.ContentType = PipePacketContentType.Json; + + stream.WritePacket(ref header, JsonSerializer.SerializeToUtf8Bytes(data)); + } + + public static void WritePacket(this PipeStream stream, ref PipePacketHeader header, byte[] content) + { + header.ContentLength = content.Length; + header.Checksum = XxHash64.HashToUInt64(content); + + stream.WritePacket(in header); + stream.Write(content); + } + + public static void WritePacket(this PipeStream stream, byte version, PipePacketType type, PipePacketCommand command) + { + PipePacketHeader header = default; + header.Version = version; + header.Type = type; + header.Command = command; + + stream.WritePacket(in header); + } + + public static unsafe void WritePacket(this PipeStream stream, ref readonly PipePacketHeader header) + { + fixed (PipePacketHeader* pHeader = &header) + { + stream.Write(new(pHeader, sizeof(PipePacketHeader))); + } + } +} \ No newline at end of file