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.

210 lines
7.8 KiB

  1. using HarmonyLib;
  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 readonly 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. if (!WinConsole.UseVTEscapes) return "";
  48. string code = "0"; // reset
  49. switch (col)
  50. {
  51. case ConsoleColor.Black:
  52. code = "30";
  53. break;
  54. case ConsoleColor.DarkBlue:
  55. code = "34";
  56. break;
  57. case ConsoleColor.DarkGreen:
  58. code = "32";
  59. break;
  60. case ConsoleColor.DarkCyan:
  61. code = "36";
  62. break;
  63. case ConsoleColor.DarkRed:
  64. code = "31";
  65. break;
  66. case ConsoleColor.DarkMagenta:
  67. code = "35";
  68. break;
  69. case ConsoleColor.DarkYellow:
  70. code = "33";
  71. break;
  72. case ConsoleColor.Gray:
  73. code = "37";
  74. break;
  75. case ConsoleColor.DarkGray:
  76. code = "90"; // literally bright black
  77. break;
  78. case ConsoleColor.Blue:
  79. code = "94";
  80. break;
  81. case ConsoleColor.Green:
  82. code = "92";
  83. break;
  84. case ConsoleColor.Cyan:
  85. code = "96";
  86. break;
  87. case ConsoleColor.Red:
  88. code = "91";
  89. break;
  90. case ConsoleColor.Magenta:
  91. code = "95";
  92. break;
  93. case ConsoleColor.Yellow:
  94. code = "93";
  95. break;
  96. case ConsoleColor.White:
  97. code = "97";
  98. break;
  99. }
  100. return "\x1b[" + code + "m";
  101. }
  102. private static StdoutInterceptor stdoutInterceptor;
  103. private static StdoutInterceptor stderrInterceptor;
  104. private static class ConsoleHarmonyPatches
  105. {
  106. public static void Patch(Harmony harmony)
  107. {
  108. var console = typeof(Console);
  109. var resetColor = console.GetMethod("ResetColor");
  110. var foregroundProperty = console.GetProperty("ForegroundColor");
  111. var setFg = foregroundProperty?.GetSetMethod();
  112. var getFg = foregroundProperty?.GetGetMethod();
  113. try
  114. {
  115. if (resetColor != null)
  116. harmony.Patch(resetColor, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchResetColor)));
  117. if (foregroundProperty != null)
  118. {
  119. harmony.Patch(setFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchSetForegroundColor)));
  120. harmony.Patch(getFg, transpiler: new HarmonyMethod(typeof(ConsoleHarmonyPatches), nameof(PatchGetForegroundColor)));
  121. }
  122. }
  123. catch (Exception e)
  124. {
  125. // Harmony might be fucked because of wierdness in Guid.NewGuid, don't let that kill us
  126. Logger.log.Error("Error installing harmony patches to intercept Console color properties:");
  127. Logger.log.Error(e);
  128. }
  129. }
  130. public static ConsoleColor GetColor() => stdoutInterceptor.currentColor;
  131. public static void SetColor(ConsoleColor col) => stdoutInterceptor.currentColor = col;
  132. public static void ResetColor() => stdoutInterceptor.currentColor = defaultColor;
  133. public static IEnumerable<CodeInstruction> PatchGetForegroundColor(IEnumerable<CodeInstruction> _)
  134. {
  135. var getColorM = typeof(ConsoleHarmonyPatches).GetMethod("GetColor");
  136. return new[] {
  137. new CodeInstruction(OpCodes.Tailcall),
  138. new CodeInstruction(OpCodes.Call, getColorM),
  139. new CodeInstruction(OpCodes.Ret)
  140. };
  141. }
  142. public static IEnumerable<CodeInstruction> PatchSetForegroundColor(IEnumerable<CodeInstruction> _)
  143. {
  144. var setColorM = typeof(ConsoleHarmonyPatches).GetMethod("SetColor");
  145. return new[] {
  146. new CodeInstruction(OpCodes.Ldarg_0),
  147. new CodeInstruction(OpCodes.Tailcall),
  148. new CodeInstruction(OpCodes.Call, setColorM),
  149. new CodeInstruction(OpCodes.Ret)
  150. };
  151. }
  152. public static IEnumerable<CodeInstruction> PatchResetColor(IEnumerable<CodeInstruction> _)
  153. {
  154. var resetColor = typeof(ConsoleHarmonyPatches).GetMethod("ResetColor");
  155. return new[] {
  156. new CodeInstruction(OpCodes.Tailcall),
  157. new CodeInstruction(OpCodes.Call, resetColor),
  158. new CodeInstruction(OpCodes.Ret)
  159. };
  160. }
  161. }
  162. private static Harmony harmony;
  163. private static bool usingInterceptor = false;
  164. public static void Intercept()
  165. {
  166. if (!usingInterceptor)
  167. {
  168. usingInterceptor = true;
  169. if (harmony == null)
  170. harmony = new Harmony("BSIPA Console Redirector Patcher");
  171. if (stdoutInterceptor == null)
  172. stdoutInterceptor = new StdoutInterceptor();
  173. if (stderrInterceptor == null)
  174. stderrInterceptor = new StdoutInterceptor() { isStdErr = true };
  175. RedirectConsole();
  176. ConsoleHarmonyPatches.Patch(harmony);
  177. }
  178. }
  179. public static void RedirectConsole()
  180. {
  181. if (usingInterceptor)
  182. {
  183. Console.SetOut(stdoutInterceptor);
  184. Console.SetError(stderrInterceptor);
  185. }
  186. }
  187. }
  188. }