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.

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