mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
code style
This commit is contained in:
@@ -5,5 +5,10 @@ namespace Snap.Hutao.Core.LifeCycle.InterProcess.Model;
|
|||||||
|
|
||||||
internal sealed class ElevationStatusResponse
|
internal sealed class ElevationStatusResponse
|
||||||
{
|
{
|
||||||
|
public ElevationStatusResponse(bool isElevated)
|
||||||
|
{
|
||||||
|
IsElevated = isElevated;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsElevated { get; set; }
|
public bool IsElevated { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,47 +2,83 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO.Hashing;
|
using System.IO.Hashing;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
|
|
||||||
internal static class PipeStreamExtension
|
internal static class PipeStreamExtension
|
||||||
{
|
{
|
||||||
public static unsafe byte[] GetValidatedContent(this PipeStream stream, PipePacketHeader* header)
|
public static TData? ReadJsonContent<TData>(this PipeStream stream, ref readonly PipePacketHeader header)
|
||||||
{
|
{
|
||||||
byte[] content = new byte[header->ContentLength];
|
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(header.ContentLength))
|
||||||
stream.ReadAtLeast(content, header->ContentLength, false);
|
{
|
||||||
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
|
Span<byte> content = memoryOwner.Memory.Span[..header.ContentLength];
|
||||||
return content;
|
stream.ReadExactly(content);
|
||||||
|
|
||||||
|
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header.Checksum, "PipePacket Content Hash incorrect");
|
||||||
|
return JsonSerializer.Deserialize<TData>(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe PipePacketHeader ReadPacket<TData>(this PipeStream stream, out TData? data)
|
public static unsafe void ReadPacket<TData>(this PipeStream stream, out PipePacketHeader header, out TData? data)
|
||||||
where TData : class
|
where TData : class
|
||||||
{
|
{
|
||||||
data = default;
|
data = default;
|
||||||
|
|
||||||
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
|
stream.ReadPacket(out header);
|
||||||
stream.ReadExactly(headerSpan);
|
if (header.ContentType is PipePacketContentType.Json)
|
||||||
fixed (byte* pHeader = headerSpan)
|
|
||||||
{
|
{
|
||||||
PipePacketHeader* header = (PipePacketHeader*)pHeader;
|
data = stream.ReadJsonContent<TData>(in header);
|
||||||
if (header->ContentType is PipePacketContentType.Json)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<byte> content = stream.GetValidatedContent(header);
|
|
||||||
data = JsonSerializer.Deserialize<TData>(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *header;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe void WritePacket(this PipeStream stream, PipePacketHeader* header, byte[] content)
|
[SkipLocalsInit]
|
||||||
|
public static unsafe void ReadPacket(this PipeStream stream, out PipePacketHeader header)
|
||||||
{
|
{
|
||||||
header->ContentLength = content.Length;
|
fixed (PipePacketHeader* pHeader = &header)
|
||||||
header->Checksum = XxHash64.HashToUInt64(content);
|
{
|
||||||
|
stream.ReadExactly(new(pHeader, sizeof(PipePacketHeader)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stream.Write(new(header, sizeof(PipePacketHeader)));
|
public static unsafe 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 unsafe 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);
|
stream.Write(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe 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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
|
|
||||||
|
internal static class PrivateNamedPipe
|
||||||
|
{
|
||||||
|
public const int Version = 1;
|
||||||
|
public const string Name = "Snap.Hutao.PrivateNamedPipe";
|
||||||
|
}
|
||||||
@@ -11,62 +11,30 @@ namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
|||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
internal sealed partial class PrivateNamedPipeClient : IDisposable
|
internal sealed partial class PrivateNamedPipeClient : IDisposable
|
||||||
{
|
{
|
||||||
private readonly NamedPipeClientStream clientStream = new(".", "Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
|
private readonly NamedPipeClientStream clientStream = new(".", PrivateNamedPipe.Name, PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
|
|
||||||
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
|
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
|
||||||
{
|
{
|
||||||
if (clientStream.TryConnectOnce())
|
if (clientStream.TryConnectOnce())
|
||||||
{
|
{
|
||||||
|
clientStream.WritePacket(PrivateNamedPipe.Version, PipePacketType.Request, PipePacketCommand.RequestElevationStatus);
|
||||||
|
clientStream.ReadPacket(stackalloc byte[sizeof(PipePacketHeader)], out ElevationStatusResponse? response);
|
||||||
|
ArgumentNullException.ThrowIfNull(response);
|
||||||
|
|
||||||
|
// Prefer elevated instance
|
||||||
|
if (runtimeOptions.IsElevated && !response.IsElevated)
|
||||||
{
|
{
|
||||||
// Connect
|
// Notify previous instance to exit
|
||||||
PipePacketHeader connectPacket = default;
|
clientStream.WritePacket(PrivateNamedPipe.Version, PipePacketType.SessionTermination, PipePacketCommand.Exit);
|
||||||
connectPacket.Version = 1;
|
|
||||||
connectPacket.Type = PipePacketType.Request;
|
|
||||||
connectPacket.Command = PipePacketCommand.RequestElevationStatus;
|
|
||||||
|
|
||||||
clientStream.Write(new(&connectPacket, sizeof(PipePacketHeader)));
|
|
||||||
}
|
|
||||||
|
|
||||||
clientStream.ReadPacket(out ElevationStatusResponse? serverElevationStatus);
|
|
||||||
ArgumentNullException.ThrowIfNull(serverElevationStatus);
|
|
||||||
|
|
||||||
if (runtimeOptions.IsElevated && !serverElevationStatus.IsElevated)
|
|
||||||
{
|
|
||||||
// Kill previous instance to use current elevated instance
|
|
||||||
PipePacketHeader killPacket = default;
|
|
||||||
killPacket.Version = 1;
|
|
||||||
killPacket.Type = PipePacketType.SessionTermination;
|
|
||||||
killPacket.Command = PipePacketCommand.Exit;
|
|
||||||
|
|
||||||
clientStream.Write(new(&killPacket, sizeof(PipePacketHeader)));
|
|
||||||
clientStream.Flush();
|
clientStream.Flush();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Redirect to previous instance
|
||||||
// Redirect to previous instance
|
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
|
||||||
PipePacketHeader redirectActivationPacket = default;
|
clientStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Request, PipePacketCommand.RedirectActivation, hutaoArgs);
|
||||||
redirectActivationPacket.Version = 1;
|
clientStream.WritePacket(PrivateNamedPipe.Version, PipePacketType.SessionTermination, PipePacketCommand.None);
|
||||||
redirectActivationPacket.Type = PipePacketType.Request;
|
|
||||||
redirectActivationPacket.Command = PipePacketCommand.RedirectActivation;
|
|
||||||
redirectActivationPacket.ContentType = PipePacketContentType.Json;
|
|
||||||
|
|
||||||
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
|
|
||||||
byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(hutaoArgs);
|
|
||||||
|
|
||||||
clientStream.WritePacket(&redirectActivationPacket, jsonBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Terminate session
|
|
||||||
PipePacketHeader terminationPacket = default;
|
|
||||||
terminationPacket.Version = 1;
|
|
||||||
terminationPacket.Type = PipePacketType.SessionTermination;
|
|
||||||
|
|
||||||
clientStream.Write(new(&terminationPacket, sizeof(PipePacketHeader)));
|
|
||||||
}
|
|
||||||
|
|
||||||
clientStream.Flush();
|
clientStream.Flush();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ internal sealed partial class PrivateNamedPipeMessageDispatcher
|
|||||||
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
|
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Exit()
|
public void ExitApplication()
|
||||||
{
|
{
|
||||||
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||||
App app = serviceProvider.GetRequiredService<App>();
|
App app = serviceProvider.GetRequiredService<App>();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverStream = NamedPipeServerStreamAcl.Create(
|
serverStream = NamedPipeServerStreamAcl.Create(
|
||||||
"Snap.Hutao.PrivateNamedPipe",
|
PrivateNamedPipe.Name,
|
||||||
PipeDirection.InOut,
|
PipeDirection.InOut,
|
||||||
NamedPipeServerStream.MaxAllowedServerInstances,
|
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||||
PipeTransmissionMode.Byte,
|
PipeTransmissionMode.Byte,
|
||||||
@@ -77,42 +77,27 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
{
|
{
|
||||||
while (serverStream.IsConnected && !token.IsCancellationRequested)
|
while (serverStream.IsConnected && !token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
PipePacketHeader header = serverStream.ReadPacket(out HutaoActivationArguments? hutaoArgs);
|
serverStream.ReadPacket(out PipePacketHeader header);
|
||||||
switch ((header.Type, header.Command))
|
switch ((header.Type, header.Command))
|
||||||
{
|
{
|
||||||
case (PipePacketType.Request, PipePacketCommand.RequestElevationStatus):
|
case (PipePacketType.Request, PipePacketCommand.RequestElevationStatus):
|
||||||
RespondElevationStatus();
|
ElevationStatusResponse resp = new(runtimeOptions.IsElevated);
|
||||||
|
serverStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Response, PipePacketCommand.ResponseElevationStatus, resp);
|
||||||
|
serverStream.Flush();
|
||||||
break;
|
break;
|
||||||
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
|
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
|
||||||
|
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
|
||||||
messageDispatcher.RedirectActivation(hutaoArgs);
|
messageDispatcher.RedirectActivation(hutaoArgs);
|
||||||
break;
|
break;
|
||||||
case (PipePacketType.SessionTermination, _):
|
case (PipePacketType.SessionTermination, _):
|
||||||
serverStream.Disconnect();
|
serverStream.Disconnect();
|
||||||
if (header.Command is PipePacketCommand.Exit)
|
if (header.Command is PipePacketCommand.Exit)
|
||||||
{
|
{
|
||||||
messageDispatcher.Exit();
|
messageDispatcher.ExitApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RespondElevationStatus()
|
|
||||||
{
|
|
||||||
PipePacketHeader elevatedPacket = default;
|
|
||||||
elevatedPacket.Version = 1;
|
|
||||||
elevatedPacket.Type = PipePacketType.Response;
|
|
||||||
elevatedPacket.Command = PipePacketCommand.ResponseElevationStatus;
|
|
||||||
elevatedPacket.ContentType = PipePacketContentType.Json;
|
|
||||||
|
|
||||||
ElevationStatusResponse resp = new()
|
|
||||||
{
|
|
||||||
IsElevated = runtimeOptions.IsElevated,
|
|
||||||
};
|
|
||||||
|
|
||||||
byte[] elevatedBytes = JsonSerializer.SerializeToUtf8Bytes(resp);
|
|
||||||
serverStream.WritePacket(&elevatedPacket, elevatedBytes);
|
|
||||||
serverStream.Flush();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user