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.

193 lines
6.8 KiB

  1. using Harmony;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Reflection.Emit;
  6. using System.Text;
  7. namespace IPA.Logging
  8. {
  9. internal class StdoutInterceptor : TextWriter
  10. {
  11. public override Encoding Encoding => Encoding.Default;
  12. private bool isStdErr;
  13. public override void Write(char value)
  14. {
  15. Write(value.ToString());
  16. }
  17. private string lineBuffer = "";
  18. private object bufferLock = new object();
  19. public override void Write(string value)
  20. {
  21. lock (bufferLock)
  22. { // avoid threading issues
  23. lineBuffer += value;
  24. var parts = lineBuffer.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.None);
  25. for (int i = 0; i < parts.Length; i++)
  26. {
  27. if (i + 1 == parts.Length) // last element
  28. lineBuffer = parts[i];
  29. else
  30. {
  31. var str = parts[i];
  32. if (string.IsNullOrEmpty(str)) continue;
  33. if (!isStdErr && WinConsole.IsInitialized)
  34. str = ConsoleColorToForegroundSet(currentColor) + str;
  35. if (isStdErr)
  36. Logger.stdout.Error(str);
  37. else
  38. Logger.stdout.Info(str);
  39. }
  40. }
  41. }
  42. }
  43. private const ConsoleColor defaultColor = ConsoleColor.Gray;
  44. private ConsoleColor currentColor = defaultColor;
  45. private static string ConsoleColorToForegroundSet(ConsoleColor col)
  46. {
  47. string code = "0"; // reset
  48. switch (col)
  49. {
  50. case ConsoleColor.Black:
  51. code = "30";
  52. break;
  53. case ConsoleColor.DarkBlue:
  54. code = "34";
  55. break;
  56. case ConsoleColor.DarkGreen:
  57. code = "32";
  58. break;
  59. case ConsoleColor.DarkCyan:
  60. code = "36";
  61. break;
  62. case ConsoleColor.DarkRed:
  63. code = "31";
  64. break;
  65. case ConsoleColor.DarkMagenta:
  66. code = "35";
  67. break;
  68. case ConsoleColor.DarkYellow:
  69. code = "33";
  70. break;
  71. case ConsoleColor.Gray:
  72. code = "37";
  73. break;
  74. case ConsoleColor.DarkGray:
  75. code = "90"; // literally bright black
  76. break;
  77. case ConsoleColor.Blue:
  78. code = "94";
  79. break;
  80. case ConsoleColor.Green:
  81. code = "92";
  82. break;
  83. case ConsoleColor.Cyan:
  84. code = "96";
  85. break;
  86. case ConsoleColor.Red:
  87. code = "91";
  88. break;
  89. case ConsoleColor.Magenta:
  90. code = "95";
  91. break;
  92. case ConsoleColor.Yellow:
  93. code = "93";
  94. break;
  95. case ConsoleColor.White:
  96. code = "97";
  97. break;
  98. }
  99. return "\x1b[" + code + "m";
  100. }
  101. private static StdoutInterceptor stdoutInterceptor;
  102. private static StdoutInterceptor stderrInterceptor;
  103. private static class ConsoleHarmonyPatches
  104. {
  105. public static void Patch(HarmonyInstance harmony)
  106. {
  107. var console = typeof(Console);
  108. var resetColor = console.GetMethod("ResetColor");
  109. var foregroundProperty = console.GetProperty("ForegroundColor");
  110. var setFg = foregroundProperty.SetMethod;
  111. var getFg = foregroundProperty.GetMethod;
  112. harmony.Patch(resetColor, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchResetColor"));
  113. harmony.Patch(setFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchSetForegroundColor"));
  114. harmony.Patch(getFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), "PatchGetForegroundColor"));
  115. }
  116. public static ConsoleColor GetColor() => stdoutInterceptor.currentColor;
  117. public static void SetColor(ConsoleColor col) => stdoutInterceptor.currentColor = col;
  118. public static void ResetColor() => stdoutInterceptor.currentColor = defaultColor;
  119. public static IEnumerable<CodeInstruction> PatchGetForegroundColor(IEnumerable<CodeInstruction> _)
  120. {
  121. var getColorM = typeof(ConsoleHarmonyPatches).GetMethod("GetColor");
  122. return new[] {
  123. new CodeInstruction(OpCodes.Call, getColorM),
  124. new CodeInstruction(OpCodes.Ret)
  125. };
  126. }
  127. public static IEnumerable<CodeInstruction> PatchSetForegroundColor(IEnumerable<CodeInstruction> _)
  128. {
  129. var setColorM = typeof(ConsoleHarmonyPatches).GetMethod("SetColor");
  130. return new[] {
  131. new CodeInstruction(OpCodes.Ldarg_0),
  132. new CodeInstruction(OpCodes.Call, setColorM),
  133. new CodeInstruction(OpCodes.Ret)
  134. };
  135. }
  136. public static IEnumerable<CodeInstruction> PatchResetColor(IEnumerable<CodeInstruction> _)
  137. {
  138. var resetColor = typeof(ConsoleHarmonyPatches).GetMethod("ResetColor");
  139. return new[] {
  140. new CodeInstruction(OpCodes.Call, resetColor),
  141. new CodeInstruction(OpCodes.Ret)
  142. };
  143. }
  144. }
  145. private static HarmonyInstance harmony;
  146. private static bool usingInterceptor = false;
  147. public static void Intercept()
  148. {
  149. if (!usingInterceptor)
  150. {
  151. usingInterceptor = true;
  152. if (harmony == null)
  153. harmony = HarmonyInstance.Create("BSIPA Console Redirector Patcher");
  154. if (stdoutInterceptor == null)
  155. stdoutInterceptor = new StdoutInterceptor();
  156. if (stderrInterceptor == null)
  157. stderrInterceptor = new StdoutInterceptor() { isStdErr = true };
  158. RedirectConsole();
  159. ConsoleHarmonyPatches.Patch(harmony);
  160. }
  161. }
  162. public static void RedirectConsole()
  163. {
  164. if (usingInterceptor)
  165. {
  166. Console.SetOut(stdoutInterceptor);
  167. Console.SetError(stderrInterceptor);
  168. }
  169. }
  170. }
  171. }