You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
4.1 KiB

  1. using System;
  2. using System.IO.Pipes;
  3. using System.Runtime.InteropServices;
  4. using System.Text;
  5. using System.Threading;
  6. namespace IPA.Logging
  7. {
  8. internal static class StdoutInterceptorPipes
  9. {
  10. // Used to ensure the server starts first, as Mono struggles with this simple task.
  11. // Otherwise it would throw a ERROR_PIPE_CONNECTED Win32Exception.
  12. private static readonly ManualResetEventSlim manualResetEvent = new(false);
  13. public static bool ShouldRedirectStdHandles;
  14. public static void Initialize()
  15. {
  16. InitializePipe(StdOutputHandle);
  17. InitializePipe(StdErrorHandle);
  18. }
  19. private static void InitializePipe(int stdHandle)
  20. {
  21. // Makes sure that we won't get a ERROR_PIPE_BUSY Win32Exception
  22. // if the pipe wasn't closed fast enough when restarting the game.
  23. var pipeName = Guid.NewGuid().ToString();
  24. var serverThread = InstantiateServerThread(pipeName, stdHandle);
  25. serverThread.Start();
  26. var clientThread = InstantiateClientThread(pipeName, stdHandle);
  27. clientThread.Start();
  28. }
  29. private static Thread InstantiateServerThread(string pipeName, int stdHandle)
  30. {
  31. return new Thread(() =>
  32. {
  33. var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In);
  34. try
  35. {
  36. // If the client starts first, releases the client thread.
  37. manualResetEvent.Set();
  38. pipeServer.WaitForConnection();
  39. var buffer = new byte[1024];
  40. while (pipeServer.IsConnected)
  41. {
  42. // TODO: Figure out why this line is absolutely needed
  43. // to avoid blocking the main thread when ShouldRedirectStdHandles is false.
  44. var length = pipeServer.Read(buffer, 0, buffer.Length);
  45. if (ShouldRedirectStdHandles)
  46. {
  47. // Separate method to avoid a BadImageFormatException when accessing StdoutInterceptor early.
  48. // This happens because the Harmony DLL is not loaded at this point.
  49. WriteToInterceptor(length , buffer, stdHandle);
  50. }
  51. }
  52. }
  53. catch (Exception ex)
  54. {
  55. Console.WriteLine(ex);
  56. }
  57. pipeServer.Close();
  58. manualResetEvent.Dispose();
  59. });
  60. }
  61. private static Thread InstantiateClientThread(string pipeName, int stdHandle)
  62. {
  63. return new Thread(() =>
  64. {
  65. var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.Out);
  66. try
  67. {
  68. // If the client starts first, blocks the client thread.
  69. manualResetEvent.Wait();
  70. pipeClient.Connect();
  71. SetStdHandle(stdHandle, pipeClient.SafePipeHandle.DangerousGetHandle());
  72. while (pipeClient.IsConnected)
  73. {
  74. // Keeps the thread alive.
  75. }
  76. }
  77. catch (Exception ex)
  78. {
  79. Console.WriteLine(ex);
  80. }
  81. pipeClient.Close();
  82. manualResetEvent.Dispose();
  83. });
  84. }
  85. private static void WriteToInterceptor(int length, byte[] buffer, int stdHandle)
  86. {
  87. var interceptor = stdHandle == StdOutputHandle ? StdoutInterceptor.Stdout : StdoutInterceptor.Stderr;
  88. interceptor!.Write(Encoding.UTF8.GetString(buffer, 0, length));
  89. }
  90. [DllImport("kernel32.dll")]
  91. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  92. private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
  93. private const int StdOutputHandle = -11;
  94. private const int StdErrorHandle = -12;
  95. }
  96. }