|
|
- using System;
- using System.IO.Pipes;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
- 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(STD_OUTPUT_HANDLE);
- InitializePipe(STD_ERROR_HANDLE);
- }
-
- 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(() =>
- {
- NamedPipeServerStream pipeServer = new(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)
- {
- if (ShouldRedirectStdHandles)
- {
- // Separate method to avoid a BadImageFormatException when accessing StdoutInterceptor early.
- // This happens because the Harmony DLL is not loaded at this point.
- Redirect(pipeServer, buffer, stdHandle);
- }
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
-
- pipeServer.Close();
- manualResetEvent.Dispose();
- });
- }
-
- private static Thread InstantiateClientThread(string pipeName, int stdHandle)
- {
- return new Thread(() =>
- {
- NamedPipeClientStream pipeClient = new(".", 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 Redirect(NamedPipeServerStream server, byte[] buffer, int stdHandle)
- {
- var charsRead = server.Read(buffer, 0, buffer.Length);
- var interceptor = stdHandle == STD_OUTPUT_HANDLE ? StdoutInterceptor.Stdout : StdoutInterceptor.Stderr;
- interceptor!.Write(Encoding.UTF8.GetString(buffer, 0, charsRead));
- }
-
- [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
- [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
- [ResourceExposure(ResourceScope.Process)]
- private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
-
- private const int STD_OUTPUT_HANDLE = -11;
- private const int STD_ERROR_HANDLE = -12;
- }
- }
|