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.

106 lines
4.0 KiB

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