Add HutaoInterop Pipe

This commit is contained in:
DismissedLight 2024-10-19 15:20:58 +08:00
parent 790d42cee0
commit 1f2d3a40d9
8 changed files with 182 additions and 1 deletions

View File

@ -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<ApplicationHostService>();
// Page resolver service
services.AddSingleton<IPageService, PageService>();
@ -98,6 +103,7 @@ public partial class App : Application
services.AddHostedService(sp => sp.GetRequiredService<NotificationService>());
services.AddSingleton<NotifierManager>();
services.AddSingleton<IScriptService, ScriptService>();
services.AddSingleton<HutaoNamedPipe>();
// Configuration
//services.Configure<AppConfig>(context.Configuration.GetSection(nameof(AppConfig)));

View File

@ -64,6 +64,7 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.RichTextBoxEx.Wpf" Version="1.1.0.1" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<PackageReference Include="Vanara.PInvoke.NtDll" Version="4.0.2" />
<PackageReference Include="Vanara.PInvoke.SHCore" Version="4.0.2" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.0.2" />

View File

@ -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<bool> 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();
}
}

View File

@ -0,0 +1,6 @@
namespace BetterGenshinImpact.Hutao;
internal enum PipePacketCommand : byte
{
None = 0,
}

View File

@ -0,0 +1,7 @@
namespace BetterGenshinImpact.Hutao;
internal enum PipePacketContentType : byte
{
None = 0,
Json = 1,
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
namespace BetterGenshinImpact.Hutao;
internal enum PipePacketType : byte
{
None = 0,
Request = 1,
Response = 2,
SessionTermination = 3,
}

View File

@ -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<TData>(this PipeStream stream, ref readonly PipePacketHeader header)
{
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(header.ContentLength))
{
Span<byte> 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<TData>(content);
}
}
public static void ReadPacket<TData>(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<TData>(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<TData>(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)));
}
}
}