Browse Source

Redirect stdout and stderr to named pipes

pull/65/head
Meivyn 2 years ago
parent
commit
a7ae09c159
No known key found for this signature in database GPG Key ID: 8BDD3E48158B2F71
3 changed files with 115 additions and 9 deletions
  1. +2
    -0
      IPA.Injector/Injector.cs
  2. +10
    -9
      IPA.Loader/Logging/StdoutInterceptor.cs
  3. +103
    -0
      IPA.Loader/Logging/StdoutInterceptorPipes.cs

+ 2
- 0
IPA.Injector/Injector.cs View File

@ -48,6 +48,8 @@ namespace IPA.Injector
var arguments = Environment.GetCommandLineArgs(); var arguments = Environment.GetCommandLineArgs();
MaybeInitializeConsole(arguments); MaybeInitializeConsole(arguments);
StdoutInterceptorPipes.Initialize();
SetupLibraryLoading(); SetupLibraryLoading();
EnsureDirectories(); EnsureDirectories();


+ 10
- 9
IPA.Loader/Logging/StdoutInterceptor.cs View File

@ -114,8 +114,8 @@ namespace IPA.Logging
return "\x1b[" + code + "m"; return "\x1b[" + code + "m";
} }
private static StdoutInterceptor? stdoutInterceptor;
private static StdoutInterceptor? stderrInterceptor;
internal static StdoutInterceptor? Stdout;
internal static StdoutInterceptor? Stderr;
private static class ConsoleHarmonyPatches private static class ConsoleHarmonyPatches
{ {
@ -145,9 +145,9 @@ namespace IPA.Logging
} }
} }
public static ConsoleColor GetColor() => stdoutInterceptor!.currentColor;
public static void SetColor(ConsoleColor col) => stdoutInterceptor!.currentColor = col;
public static void ResetColor() => stdoutInterceptor!.currentColor = defaultColor;
public static ConsoleColor GetColor() => Stdout!.currentColor;
public static void SetColor(ConsoleColor col) => Stdout!.currentColor = col;
public static void ResetColor() => Stdout!.currentColor = defaultColor;
public static IEnumerable<CodeInstruction> PatchGetForegroundColor(IEnumerable<CodeInstruction> _) public static IEnumerable<CodeInstruction> PatchGetForegroundColor(IEnumerable<CodeInstruction> _)
{ {
@ -194,8 +194,8 @@ namespace IPA.Logging
HarmonyGlobalSettings.DisallowLegacyGlobalUnpatchAll = true; HarmonyGlobalSettings.DisallowLegacyGlobalUnpatchAll = true;
harmony ??= new Harmony("BSIPA Console Redirector Patcher"); harmony ??= new Harmony("BSIPA Console Redirector Patcher");
stdoutInterceptor ??= new StdoutInterceptor();
stderrInterceptor ??= new StdoutInterceptor { isStdErr = true };
Stdout ??= new StdoutInterceptor();
Stderr ??= new StdoutInterceptor { isStdErr = true };
RedirectConsole(); RedirectConsole();
ConsoleHarmonyPatches.Patch(harmony); ConsoleHarmonyPatches.Patch(harmony);
@ -206,8 +206,9 @@ namespace IPA.Logging
{ {
if (usingInterceptor) if (usingInterceptor)
{ {
Console.SetOut(stdoutInterceptor);
Console.SetError(stderrInterceptor);
Console.SetOut(Stdout);
Console.SetError(Stderr);
StdoutInterceptorPipes.ShouldRedirectStdHandles = true;
} }
} }


+ 103
- 0
IPA.Loader/Logging/StdoutInterceptorPipes.cs View File

@ -0,0 +1,103 @@
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 ManualResetEvent 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)
{
var pipeName = stdHandle == STD_OUTPUT_HANDLE ? "STD_OUT_PIPE" : "STD_ERR_PIPE";
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();
}
});
}
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.WaitOne();
pipeClient.Connect();
SetStdHandle(stdHandle, pipeClient.SafePipeHandle.DangerousGetHandle());
while (pipeClient.IsConnected)
{
// Keeps the thread alive.
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
pipeClient.Close();
}
});
}
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;
}
}

Loading…
Cancel
Save