mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd0ea971dd | ||
|
|
eeab336f99 | ||
|
|
54e1b095c4 | ||
|
|
5208a2cf55 |
@@ -6,6 +6,9 @@ namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
|||||||
internal enum PipePacketCommand : byte
|
internal enum PipePacketCommand : byte
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
|
Connect = 1,
|
||||||
|
|
||||||
RedirectActivation = 10,
|
RedirectActivation = 10,
|
||||||
|
|
||||||
|
Exit = 30,
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,5 @@ internal enum PipePacketType : byte
|
|||||||
None = 0,
|
None = 0,
|
||||||
Request = 1,
|
Request = 1,
|
||||||
Response = 2,
|
Response = 2,
|
||||||
Termination = 3,
|
SessionTermination = 3,
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,21 +2,59 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Windows.AppLifecycle;
|
using Microsoft.Windows.AppLifecycle;
|
||||||
using System.IO.Hashing;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||||
|
|
||||||
[Injection(InjectAs.Singleton)]
|
[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 NamedPipeClientStream clientStream = new(".", "Snap.Hutao.PrivateNamedPipe", PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough);
|
||||||
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
|
|
||||||
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
|
public unsafe bool TryRedirectActivationTo(AppActivationArguments args)
|
||||||
{
|
{
|
||||||
if (clientStream.TryConnectOnce())
|
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;
|
PipePacketHeader redirectActivationPacket = default;
|
||||||
redirectActivationPacket.Version = 1;
|
redirectActivationPacket.Version = 1;
|
||||||
redirectActivationPacket.Type = PipePacketType.Request;
|
redirectActivationPacket.Type = PipePacketType.Request;
|
||||||
@@ -26,17 +64,14 @@ internal sealed class PrivateNamedPipeClient : IDisposable
|
|||||||
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
|
HutaoActivationArguments hutaoArgs = HutaoActivationArguments.FromAppActivationArguments(args, isRedirected: true);
|
||||||
byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(hutaoArgs);
|
byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(hutaoArgs);
|
||||||
|
|
||||||
redirectActivationPacket.ContentLength = jsonBytes.Length;
|
clientStream.WritePacket(&redirectActivationPacket, jsonBytes);
|
||||||
redirectActivationPacket.Checksum = XxHash64.HashToUInt64(jsonBytes);
|
|
||||||
|
|
||||||
clientStream.Write(new(&redirectActivationPacket, sizeof(PipePacketHeader)));
|
|
||||||
clientStream.Write(jsonBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// Terminate session
|
||||||
PipePacketHeader terminationPacket = default;
|
PipePacketHeader terminationPacket = default;
|
||||||
terminationPacket.Version = 1;
|
terminationPacket.Version = 1;
|
||||||
terminationPacket.Type = PipePacketType.Termination;
|
terminationPacket.Type = PipePacketType.SessionTermination;
|
||||||
|
|
||||||
clientStream.Write(new(&terminationPacket, sizeof(PipePacketHeader)));
|
clientStream.Write(new(&terminationPacket, sizeof(PipePacketHeader)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,11 @@ internal sealed partial class PrivateNamedPipeMessageDispatcher
|
|||||||
|
|
||||||
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
|
serviceProvider.GetRequiredService<IAppActivation>().Activate(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Exit()
|
||||||
|
{
|
||||||
|
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||||
|
App app = serviceProvider.GetRequiredService<App>();
|
||||||
|
taskContext.BeginInvokeOnMainThread(app.Exit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
using System.IO.Hashing;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
@@ -14,6 +12,8 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
{
|
{
|
||||||
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
|
private readonly PrivateNamedPipeMessageDispatcher messageDispatcher;
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
|
private readonly ITaskContext taskContext;
|
||||||
|
private readonly App app;
|
||||||
|
|
||||||
private readonly CancellationTokenSource serverTokenSource = new();
|
private readonly CancellationTokenSource serverTokenSource = new();
|
||||||
private readonly SemaphoreSlim serverSemaphore = new(1);
|
private readonly SemaphoreSlim serverSemaphore = new(1);
|
||||||
@@ -24,6 +24,8 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
{
|
{
|
||||||
messageDispatcher = serviceProvider.GetRequiredService<PrivateNamedPipeMessageDispatcher>();
|
messageDispatcher = serviceProvider.GetRequiredService<PrivateNamedPipeMessageDispatcher>();
|
||||||
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||||
|
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||||
|
app = serviceProvider.GetRequiredService<App>();
|
||||||
|
|
||||||
PipeSecurity? pipeSecurity = default;
|
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)
|
private unsafe void RunPacketSession(NamedPipeServerStream serverStream, CancellationToken token)
|
||||||
{
|
{
|
||||||
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
|
Span<byte> headerSpan = stackalloc byte[sizeof(PipePacketHeader)];
|
||||||
@@ -95,13 +89,28 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
|
|||||||
|
|
||||||
switch ((header->Type, header->Command, header->ContentType))
|
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):
|
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));
|
messageDispatcher.RedirectActivation(JsonSerializer.Deserialize<HutaoActivationArguments>(content));
|
||||||
break;
|
break;
|
||||||
case (PipePacketType.Termination, _, _):
|
case (PipePacketType.SessionTermination, _, _):
|
||||||
serverStream.Disconnect();
|
serverStream.Disconnect();
|
||||||
sessionTerminated = true;
|
sessionTerminated = true;
|
||||||
|
if (header->Command is PipePacketCommand.Exit)
|
||||||
|
{
|
||||||
|
messageDispatcher.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user