diff --git a/BuildTools b/BuildTools
index de919c49..af3be92b 160000
--- a/BuildTools
+++ b/BuildTools
@@ -1 +1 @@
-Subproject commit de919c49496a7e7e11382bd876473018ddceaa1a
+Subproject commit af3be92b829e0068671666d74b25fb7f2ec4b520
diff --git a/Doorstop b/Doorstop
index 6b6de3b8..642c22e3 160000
--- a/Doorstop
+++ b/Doorstop
@@ -1 +1 @@
-Subproject commit 6b6de3b81cf142c73a8ef845f705fb1e51a7670d
+Subproject commit 642c22e38a76bbdaa46dbb3aaa565c354a2ef68e
diff --git a/IPA.Injector/IPA.Injector.csproj b/IPA.Injector/IPA.Injector.csproj
index 14e11358..c7cc4189 100644
--- a/IPA.Injector/IPA.Injector.csproj
+++ b/IPA.Injector/IPA.Injector.csproj
@@ -14,7 +14,6 @@
true
$(SolutionDir)=C:\
portable
-
true
diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs
index fae10e9f..0649a4f6 100644
--- a/IPA.Injector/Injector.cs
+++ b/IPA.Injector/Injector.cs
@@ -239,7 +239,7 @@ namespace IPA.Injector
};
// need to reinit streams singe Unity seems to redirect stdout
- WinConsole.InitializeStreams();
+ StdoutInterceptor.RedirectConsole();
var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent();
bootstrapper.Destroyed += Bootstrapper_Destroyed;
diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj
index b839eea7..73f38bb1 100644
--- a/IPA.Loader/IPA.Loader.csproj
+++ b/IPA.Loader/IPA.Loader.csproj
@@ -14,7 +14,6 @@
true
$(SolutionDir)=C:\
portable
-
true
@@ -39,6 +38,9 @@
bin\Release\IPA.Loader.xml
+
+ ..\Libs\0Harmony.dll
+
@@ -76,6 +78,7 @@
+
diff --git a/IPA.Loader/Logging/ConsoleWindow.cs b/IPA.Loader/Logging/ConsoleWindow.cs
index f8e655ae..d9822106 100644
--- a/IPA.Loader/Logging/ConsoleWindow.cs
+++ b/IPA.Loader/Logging/ConsoleWindow.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
@@ -51,6 +52,16 @@ namespace IPA.Logging
ConOut = writer;
Console.SetOut(writer);
Console.SetError(writer);
+
+ var handle = GetStdHandle(-11); // get stdout handle (should be CONOUT$ at this point)
+ if (GetConsoleMode(handle, out var mode))
+ {
+ mode |= EnableVTProcessing;
+ if (!SetConsoleMode(handle, mode))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ else
+ throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
@@ -108,6 +119,17 @@ namespace IPA.Logging
IntPtr hTemplateFile
);
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern IntPtr GetStdHandle(int nStdHandle);
+
+ private const uint EnableVTProcessing = 0x0004;
+
private const uint GenericWrite = 0x40000000;
private const uint GenericRead = 0x80000000;
private const uint FileShareRead = 0x00000001;
diff --git a/IPA.Loader/Logging/Logger.cs b/IPA.Loader/Logging/Logger.cs
index 51438faf..8565bcd1 100644
--- a/IPA.Loader/Logging/Logger.cs
+++ b/IPA.Loader/Logging/Logger.cs
@@ -21,6 +21,18 @@ namespace IPA.Logging
}
}
+ private static StandardLogger _stdout;
+
+ internal static StandardLogger stdout
+ {
+ get
+ {
+ if (_stdout == null)
+ _stdout = new StandardLogger("_");
+ return _stdout;
+ }
+ }
+
internal static Logger updater => log.GetChildLogger("Updater");
internal static Logger libLoader => log.GetChildLogger("LibraryLoader");
internal static Logger loader => log.GetChildLogger("Loader");
diff --git a/IPA.Loader/Logging/Printers/ColorlessConsolePrinter.cs b/IPA.Loader/Logging/Printers/ColorlessConsolePrinter.cs
index 754b75ed..de346b70 100644
--- a/IPA.Loader/Logging/Printers/ColorlessConsolePrinter.cs
+++ b/IPA.Loader/Logging/Printers/ColorlessConsolePrinter.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace IPA.Logging.Printers
{
diff --git a/IPA.Loader/Logging/Printers/GZFilePrinter.cs b/IPA.Loader/Logging/Printers/GZFilePrinter.cs
index 5f39d9cc..94f095e7 100644
--- a/IPA.Loader/Logging/Printers/GZFilePrinter.cs
+++ b/IPA.Loader/Logging/Printers/GZFilePrinter.cs
@@ -3,6 +3,7 @@ using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
+using System.Text.RegularExpressions;
namespace IPA.Logging.Printers
{
@@ -18,6 +19,8 @@ namespace IPA.Logging.Printers
IntPtr lpSecurityAttributes
);
+ internal static Regex removeControlCodes = new Regex("\x1b\\[\\d+m", RegexOptions.Compiled);
+
private FileInfo fileInfo;
///
@@ -45,7 +48,7 @@ namespace IPA.Logging.Printers
fileInfo = new FileInfo(fileInfo.FullName + ".gz");
fileInfo.Create().Close();
- var symlink = new FileInfo(Path.Combine(fileInfo.DirectoryName ?? throw new InvalidOperationException(), $"latest{ext}.gz"));
+ var symlink = new FileInfo(Path.Combine(fileInfo.DirectoryName ?? throw new InvalidOperationException(), $"_latest{ext}.gz"));
if (symlink.Exists) symlink.Delete();
try
diff --git a/IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs b/IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs
index 59329a54..009f3e92 100644
--- a/IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs
+++ b/IPA.Loader/Logging/Printers/GlobalLogFilePrinter.cs
@@ -22,7 +22,7 @@ namespace IPA.Logging.Printers
/// the message to print
public override void Print(Logger.Level level, DateTime time, string logName, string message)
{
- foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var line in removeControlCodes.Replace(message, "").Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
}
diff --git a/IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs b/IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs
index eda96b21..f7d2770f 100644
--- a/IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs
+++ b/IPA.Loader/Logging/Printers/PluginLogFilePrinter.cs
@@ -45,7 +45,7 @@ namespace IPA.Logging.Printers
/// the message to print
public override void Print(Logger.Level level, DateTime time, string logName, string message)
{
- foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var line in removeControlCodes.Replace(message, "").Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
}
}
diff --git a/IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs b/IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs
index 54bdead2..5446f46f 100644
--- a/IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs
+++ b/IPA.Loader/Logging/Printers/PluginSubLogPrinter.cs
@@ -48,7 +48,7 @@ namespace IPA.Logging.Printers
/// the message to print
public override void Print(Logger.Level level, DateTime time, string logName, string message)
{
- foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var line in removeControlCodes.Replace(message, "").Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
FileWriter.WriteLine("[{2} @ {1:HH:mm:ss}] {0}", line, time, level.ToString().ToUpper());
}
}
diff --git a/IPA.Loader/Logging/StandardLogger.cs b/IPA.Loader/Logging/StandardLogger.cs
index 197c3ac9..1672bcfb 100644
--- a/IPA.Loader/Logging/StandardLogger.cs
+++ b/IPA.Loader/Logging/StandardLogger.cs
@@ -71,6 +71,7 @@ namespace IPA.Logging
Color = ConsoleColor.Magenta,
}
});
+
addedConsolePrinters = true;
}
}
@@ -135,7 +136,8 @@ namespace IPA.Logging
{
if (!addedConsolePrinters)
AddDefaultPrinter(new ColorlessConsolePrinter());
-
+
+ StdoutInterceptor.Intercept();
finalizedDefaultPrinters = true;
}
diff --git a/IPA.Loader/Logging/StdoutInterceptor.cs b/IPA.Loader/Logging/StdoutInterceptor.cs
new file mode 100644
index 00000000..88693ce3
--- /dev/null
+++ b/IPA.Loader/Logging/StdoutInterceptor.cs
@@ -0,0 +1,190 @@
+using Harmony;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection.Emit;
+using System.Text;
+
+namespace IPA.Logging
+{
+ internal class StdoutInterceptor : TextWriter
+ {
+ public override Encoding Encoding => Encoding.Default;
+
+ private bool isStdErr;
+
+ public override void Write(char value)
+ {
+ Write(value.ToString());
+ }
+
+ private string lineBuffer = "";
+
+ public override void Write(string value)
+ {
+ lineBuffer += value;
+
+ var parts = lineBuffer.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.None);
+ for (int i = 0; i < parts.Length; i++)
+ {
+ if (i + 1 == parts.Length) // last element
+ lineBuffer = parts[i];
+ else
+ {
+ var str = parts[i];
+ if (string.IsNullOrEmpty(str)) continue;
+ if (!isStdErr && WinConsole.IsInitialized)
+ str = ConsoleColorToForegroundSet(currentColor) + str;
+
+ if (isStdErr)
+ Logger.stdout.Error(str);
+ else
+ Logger.stdout.Info(str);
+ }
+
+ }
+ }
+
+ private const ConsoleColor defaultColor = ConsoleColor.Gray;
+ private ConsoleColor currentColor = defaultColor;
+
+ private static string ConsoleColorToForegroundSet(ConsoleColor col)
+ {
+ string code = "0"; // reset
+
+ switch (col)
+ {
+ case ConsoleColor.Black:
+ code = "30";
+ break;
+ case ConsoleColor.DarkBlue:
+ code = "34";
+ break;
+ case ConsoleColor.DarkGreen:
+ code = "32";
+ break;
+ case ConsoleColor.DarkCyan:
+ code = "36";
+ break;
+ case ConsoleColor.DarkRed:
+ code = "31";
+ break;
+ case ConsoleColor.DarkMagenta:
+ code = "35";
+ break;
+ case ConsoleColor.DarkYellow:
+ code = "33";
+ break;
+ case ConsoleColor.Gray:
+ code = "37";
+ break;
+ case ConsoleColor.DarkGray:
+ code = "90"; // literally bright black
+ break;
+ case ConsoleColor.Blue:
+ code = "94";
+ break;
+ case ConsoleColor.Green:
+ code = "92";
+ break;
+ case ConsoleColor.Cyan:
+ code = "96";
+ break;
+ case ConsoleColor.Red:
+ code = "91";
+ break;
+ case ConsoleColor.Magenta:
+ code = "95";
+ break;
+ case ConsoleColor.Yellow:
+ code = "93";
+ break;
+ case ConsoleColor.White:
+ code = "97";
+ break;
+ }
+
+ return "\x1b[" + code + "m";
+ }
+
+ private static StdoutInterceptor stdoutInterceptor;
+ private static StdoutInterceptor stderrInterceptor;
+
+ private static class ConsoleHarmonyPatches
+ {
+ public static void Patch(HarmonyInstance harmony)
+ {
+ var console = typeof(Console);
+ var resetColor = console.GetMethod("ResetColor");
+ var foregroundProperty = console.GetProperty("ForegroundColor");
+ var setFg = foregroundProperty.SetMethod;
+ var getFg = foregroundProperty.GetMethod;
+
+ harmony.Patch(resetColor, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchResetColor"));
+ harmony.Patch(setFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchSetForegroundColor"));
+ harmony.Patch(getFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchGetForegroundColor"));
+ }
+
+ public static ConsoleColor GetColor() => stdoutInterceptor.currentColor;
+ public static void SetColor(ConsoleColor col) => stdoutInterceptor.currentColor = col;
+ public static void ResetColor() => stdoutInterceptor.currentColor = defaultColor;
+
+ public static IEnumerable PatchGetForegroundColor(IEnumerable _)
+ {
+ var getColorM = typeof(ConsoleHarmonyPatches).GetMethod("GetColor");
+ return new[] {
+ new CodeInstruction(OpCodes.Call, getColorM),
+ new CodeInstruction(OpCodes.Ret)
+ };
+ }
+
+ public static IEnumerable PatchSetForegroundColor(IEnumerable _)
+ {
+ var setColorM = typeof(ConsoleHarmonyPatches).GetMethod("SetColor");
+ return new[] {
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new CodeInstruction(OpCodes.Call, setColorM),
+ new CodeInstruction(OpCodes.Ret)
+ };
+ }
+
+ public static IEnumerable PatchResetColor(IEnumerable _)
+ {
+ var resetColor = typeof(ConsoleHarmonyPatches).GetMethod("ResetColor");
+ return new[] {
+ new CodeInstruction(OpCodes.Call, resetColor),
+ new CodeInstruction(OpCodes.Ret)
+ };
+ }
+ }
+
+ private static HarmonyInstance harmony;
+ private static bool usingInterceptor = false;
+
+ public static void Intercept()
+ {
+ if (!usingInterceptor)
+ {
+ usingInterceptor = true;
+ if (harmony == null)
+ harmony = HarmonyInstance.Create("BSIPA Console Redirector Patcher");
+ if (stdoutInterceptor == null)
+ stdoutInterceptor = new StdoutInterceptor();
+ if (stderrInterceptor == null)
+ stderrInterceptor = new StdoutInterceptor() { isStdErr = true };
+
+ RedirectConsole();
+ ConsoleHarmonyPatches.Patch(harmony);
+ }
+ }
+
+ public static void RedirectConsole()
+ {
+ if (usingInterceptor)
+ {
+ Console.SetOut(stdoutInterceptor);
+ Console.SetError(stderrInterceptor);
+ }
+ }
+ }
+}
diff --git a/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache b/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache
index d16fba0d..5550c3c6 100644
--- a/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache
+++ b/IPA/obj/Debug/IPA.csproj.CoreCompileInputs.cache
@@ -1 +1 @@
-5d76d76cc5c14257f2b9071c928c27b3edd80cc0
+b1078c6b90a14bd4bd5a5e7f04507fbb89e2b3a7
diff --git a/README.md b/README.md
index 55150a1f..3ee33bec 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,9 @@ BSIPA will automatically repatch the game when it updates, as long as `winhttp.d
### Prerequisites
-- Microsoft Visual Studio 2017 or later
-- Tools for C/C++ (MSVC)
-- .NET 4.6 SDK and .NET 4.7.1 SDK
+- Microsoft Visual Studio 2019 or later
+- Tools for C/C++ (MSVC) v142
+- .NET 4.6.1 SDK and .NET 4.7.2 SDK
### Building