Compare commits

...

4 Commits

Author SHA1 Message Date
qhy040404
dd0ea971dd code style 2024-05-31 23:31:50 +08:00
qhy040404
eeab336f99 use client to determine whether to redirect 2024-05-31 23:30:25 +08:00
qhy040404
54e1b095c4 auto constructor 2024-05-31 22:16:36 +08:00
qhy040404
5208a2cf55 impl #1663 2024-05-31 22:03:57 +08:00
6 changed files with 103 additions and 21 deletions

View File

@@ -6,6 +6,9 @@ namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal enum PipePacketCommand : byte
{
None = 0,
Connect = 1,
RedirectActivation = 10,
Exit = 30,
}

View File

@@ -8,5 +8,5 @@ internal enum PipePacketType : byte
None = 0,
Request = 1,
Response = 2,
Termination = 3,
SessionTermination = 3,
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using System.IO.Hashing;
using System.IO.Pipes;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
internal static class PipeStreamExtension
{
public static unsafe byte[] GetValidatedContent(this PipeStream stream, PipePacketHeader* header)
{
byte[] content = new byte[header->ContentLength];
stream.ReadAtLeast(content, header->ContentLength, false);
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
return content;
}
public static unsafe void WritePacket(this PipeStream stream, PipePacketHeader* header, byte[] content)
{
header->ContentLength = content.Length;
header->Checksum = XxHash64.HashToUInt64(content);
stream.Write(new(header, sizeof(PipePacketHeader)));
stream.Write(content);
}
}

View File

@@ -2,21 +2,59 @@
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
using System.IO.Hashing;
using System.IO.Pipes;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
[Injection(InjectAs.Singleton)]
internal sealed class PrivateNamedPipeClient : IDisposable
[ConstructorGenerated]
internal sealed partial class PrivateNamedPipeClient : IDisposable
{
private readonly NamedPipeClientStream clientStream = new(".", "Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
private readonly RuntimeOptions runtimeOptions;
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
{
if (clientStream.TryConnectOnce())
{
bool serverElevated = false;
{
// Connect
PipePacketHeader connectPacket = default;
connectPacket.Version = 1;
connectPacket.Type = PipePacketType.Request;
connectPacket.Command = PipePacketCommand.Connect;
clientStream.Write(new(&connectPacket, sizeof(PipePacketHeader)));
}
{
// Get previous instance elevated status
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
clientStream.ReadExactly(headerSpan);
fixed (byte* pHeader = headerSpan)
{
PipePacketHeader* header = (PipePacketHeader*)pHeader;
ReadOnlySpan<byte> content = clientStream.GetValidatedContent(header);
serverElevated = JsonSerializer.Deserialize<bool>(content);
}
}
if (!serverElevated && runtimeOptions.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();
return false;
}
{
// Redirect to previous instance
PipePacketHeader redirectActivationPacket = default;
redirectActivationPacket.Version = 1;
redirectActivationPacket.Type = PipePacketType.Request;
@@ -26,17 +64,14 @@ internal sealed class PrivateNamedPipeClient : IDisposable
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(hutaoArgs);
redirectActivationPacket.ContentLength = jsonBytes.Length;
redirectActivationPacket.Checksum = XxHash64.HashToUInt64(jsonBytes);
clientStream.Write(new(&redirectActivationPacket, sizeof(PipePacketHeader)));
clientStream.Write(jsonBytes);
clientStream.WritePacket(&redirectActivationPacket, jsonBytes);
}
{
// Terminate session
PipePacketHeader terminationPacket = default;
terminationPacket.Version = 1;
terminationPacket.Type = PipePacketType.Termination;
terminationPacket.Type = PipePacketType.SessionTermination;
clientStream.Write(new(&terminationPacket, sizeof(PipePacketHeader)));
}

View File

@@ -18,4 +18,11 @@ internal sealed partial class PrivateNamedPipeMessageDispatcher
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
}
public void Exit()
{
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
App app = serviceProvider.GetRequiredService<App>();
taskContext.BeginInvokeOnMainThread(app.Exit);
}
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using System.IO.Hashing;
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;
@@ -14,6 +12,8 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly App app;
private readonly CancellationTokenSource serverTokenSource = new();
private readonly SemaphoreSlim serverSemaphore = new(1);
@@ -24,6 +24,8 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
{
messageDispatcher = serviceProvider.GetRequiredService<PrivateNamedPipeMessageDispatcher>();
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
app = serviceProvider.GetRequiredService<App>();
PipeSecurity? pipeSecurity = default;
@@ -74,14 +76,6 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
}
}
private static unsafe byte[] GetValidatedContent(NamedPipeServerStream serverStream, PipePacketHeader* header)
{
byte[] content = new byte[header->ContentLength];
serverStream.ReadAtLeast(content, header->ContentLength, false);
HutaoException.ThrowIf(XxHash64.HashToUInt64(content) != header->Checksum, "PipePacket Content Hash incorrect");
return content;
}
private unsafe void RunPacketSession(NamedPipeServerStream serverStream, CancellationToken token)
{
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
@@ -95,13 +89,28 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
switch ((header->Type, header->Command, header->ContentType))
{
case (PipePacketType.Request, PipePacketCommand.Connect, _):
PipePacketHeader elevatedPacket = default;
elevatedPacket.Version = 1;
elevatedPacket.Type = PipePacketType.Response;
elevatedPacket.ContentType = PipePacketContentType.Json;
byte[] elevatedBytes = JsonSerializer.SerializeToUtf8Bytes(runtimeOptions.IsElevated);
serverStream.WritePacket(&elevatedPacket, elevatedBytes);
break;
case (PipePacketType.Request, PipePacketCommand.RedirectActivation, PipePacketContentType.Json):
ReadOnlySpan<byte> content = GetValidatedContent(serverStream, header);
ReadOnlySpan<byte> content = serverStream.GetValidatedContent(header);
messageDispatcher.RedirectActivation(JsonSerializer.Deserialize<HutaoActivationArguments>(content));
break;
case (PipePacketType.Termination, _, _):
case (PipePacketType.SessionTermination, _, _):
serverStream.Disconnect();
sessionTerminated = true;
if (header->Command is PipePacketCommand.Exit)
{
messageDispatcher.Exit();
}
return;
}
}