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.

102 lines
3.8 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 ManualResetEvent 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. var pipeName = stdHandle == STD_OUTPUT_HANDLE ? "STD_OUT_PIPE" : "STD_ERR_PIPE";
  23. var serverThread = InstantiateServerThread(pipeName, stdHandle);
  24. serverThread.Start();
  25. var clientThread = InstantiateClientThread(pipeName, stdHandle);
  26. clientThread.Start();
  27. }
  28. private static Thread InstantiateServerThread(string pipeName, int stdHandle)
  29. {
  30. return new Thread(() =>
  31. {
  32. NamedPipeServerStream pipeServer = new(pipeName, PipeDirection.In);
  33. try
  34. {
  35. // If the client starts first, releases the client thread.
  36. manualResetEvent.Set();
  37. pipeServer.WaitForConnection();
  38. var buffer = new byte[1024];
  39. while (pipeServer.IsConnected)
  40. {
  41. if (ShouldRedirectStdHandles)
  42. {
  43. // Separate method to avoid a BadImageFormatException when accessing StdoutInterceptor early.
  44. // This happens because the Harmony DLL is not loaded at this point.
  45. Redirect(pipeServer, buffer, stdHandle);
  46. }
  47. }
  48. }
  49. catch (Exception ex)
  50. {
  51. Console.WriteLine(ex);
  52. pipeServer.Close();
  53. }
  54. });
  55. }
  56. private static Thread InstantiateClientThread(string pipeName, int stdHandle)
  57. {
  58. return new Thread(() =>
  59. {
  60. NamedPipeClientStream pipeClient = new(".", pipeName, PipeDirection.Out);
  61. try
  62. {
  63. // If the client starts first, blocks the client thread.
  64. manualResetEvent.WaitOne();
  65. pipeClient.Connect();
  66. SetStdHandle(stdHandle, pipeClient.SafePipeHandle.DangerousGetHandle());
  67. while (pipeClient.IsConnected)
  68. {
  69. // Keeps the thread alive.
  70. }
  71. }
  72. catch (Exception ex)
  73. {
  74. Console.WriteLine(ex);
  75. pipeClient.Close();
  76. }
  77. });
  78. }
  79. private static void Redirect(NamedPipeServerStream server, byte[] buffer, int stdHandle)
  80. {
  81. var charsRead = server.Read(buffer, 0, buffer.Length);
  82. var interceptor = stdHandle == STD_OUTPUT_HANDLE ? StdoutInterceptor.Stdout : StdoutInterceptor.Stderr;
  83. interceptor!.Write(Encoding.UTF8.GetString(buffer, 0, charsRead));
  84. }
  85. [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
  86. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  87. [ResourceExposure(ResourceScope.Process)]
  88. private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
  89. private const int STD_OUTPUT_HANDLE = -11;
  90. private const int STD_ERROR_HANDLE = -12;
  91. }
  92. }