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