using System;
|
|
using System.IO.Pipes;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace IPA.Logging
|
|
{
|
|
internal static class StdoutInterceptorPipes
|
|
{
|
|
// Used to ensure the server starts first, as Mono struggles with this simple task.
|
|
// Otherwise it would throw a ERROR_PIPE_CONNECTED Win32Exception.
|
|
private static readonly ManualResetEventSlim manualResetEvent = new(false);
|
|
|
|
public static bool ShouldRedirectStdHandles;
|
|
|
|
public static void Initialize()
|
|
{
|
|
InitializePipe(StdOutputHandle);
|
|
InitializePipe(StdErrorHandle);
|
|
}
|
|
|
|
private static void InitializePipe(int stdHandle)
|
|
{
|
|
// Makes sure that we won't get a ERROR_PIPE_BUSY Win32Exception
|
|
// if the pipe wasn't closed fast enough when restarting the game.
|
|
var pipeName = Guid.NewGuid().ToString();
|
|
var serverThread = InstantiateServerThread(pipeName, stdHandle);
|
|
serverThread.Start();
|
|
var clientThread = InstantiateClientThread(pipeName, stdHandle);
|
|
clientThread.Start();
|
|
}
|
|
|
|
private static Thread InstantiateServerThread(string pipeName, int stdHandle)
|
|
{
|
|
return new Thread(() =>
|
|
{
|
|
var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In);
|
|
|
|
try
|
|
{
|
|
// If the client starts first, releases the client thread.
|
|
manualResetEvent.Set();
|
|
pipeServer.WaitForConnection();
|
|
var buffer = new byte[1024];
|
|
while (pipeServer.IsConnected)
|
|
{
|
|
// TODO: Figure out why this line is absolutely needed
|
|
// to avoid blocking the main thread when ShouldRedirectStdHandles is false.
|
|
var length = pipeServer.Read(buffer, 0, buffer.Length);
|
|
if (ShouldRedirectStdHandles)
|
|
{
|
|
// Separate method to avoid a BadImageFormatException when accessing StdoutInterceptor early.
|
|
// This happens because the Harmony DLL is not loaded at this point.
|
|
WriteToInterceptor(length , buffer, stdHandle);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex);
|
|
}
|
|
|
|
pipeServer.Close();
|
|
manualResetEvent.Dispose();
|
|
});
|
|
}
|
|
|
|
private static Thread InstantiateClientThread(string pipeName, int stdHandle)
|
|
{
|
|
return new Thread(() =>
|
|
{
|
|
var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.Out);
|
|
|
|
try
|
|
{
|
|
// If the client starts first, blocks the client thread.
|
|
manualResetEvent.Wait();
|
|
pipeClient.Connect();
|
|
SetStdHandle(stdHandle, pipeClient.SafePipeHandle.DangerousGetHandle());
|
|
while (pipeClient.IsConnected)
|
|
{
|
|
// Keeps the thread alive.
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex);
|
|
}
|
|
|
|
pipeClient.Close();
|
|
manualResetEvent.Dispose();
|
|
});
|
|
}
|
|
|
|
private static void WriteToInterceptor(int length, byte[] buffer, int stdHandle)
|
|
{
|
|
var interceptor = stdHandle == StdOutputHandle ? StdoutInterceptor.Stdout : StdoutInterceptor.Stderr;
|
|
interceptor!.Write(Encoding.UTF8.GetString(buffer, 0, length));
|
|
}
|
|
|
|
[DllImport("kernel32.dll")]
|
|
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
|
private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
|
|
|
|
private const int StdOutputHandle = -11;
|
|
private const int StdErrorHandle = -12;
|
|
}
|
|
}
|