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.

139 lines
5.4 KiB

6 years ago
  1. using System;
  2. using System.ComponentModel;
  3. using System.IO;
  4. using System.Runtime.InteropServices;
  5. using Microsoft.Win32.SafeHandles;
  6. namespace IPA.Logging
  7. {
  8. // https://stackoverflow.com/a/48864902/3117125
  9. internal static class WinConsole
  10. {
  11. internal static TextWriter ConOut;
  12. internal static TextReader ConIn;
  13. private static SafeFileHandle outHandle;
  14. private static SafeFileHandle inHandle;
  15. public static bool UseVTEscapes { get; private set; } = true;
  16. internal static IntPtr OutHandle => outHandle.DangerousGetHandle();
  17. internal static IntPtr InHandle => inHandle.DangerousGetHandle();
  18. internal static bool IsInitialized;
  19. public static void Initialize(int processId, bool alwaysCreateNewConsole = false)
  20. {
  21. bool consoleAttached;
  22. if (alwaysCreateNewConsole || !(consoleAttached = AttachConsole(processId)))
  23. {
  24. consoleAttached = AllocConsole();
  25. }
  26. if (consoleAttached)
  27. {
  28. InitializeStreams();
  29. IsInitialized = true;
  30. }
  31. }
  32. private static void InitializeStreams()
  33. {
  34. InitializeOutStream();
  35. InitializeInStream();
  36. }
  37. private static void InitializeOutStream()
  38. {
  39. var fs = CreateFileStream("CONOUT$", GenericWrite, FileShareWrite, FileAccess.Write, out outHandle);
  40. if (fs != null)
  41. {
  42. var writer = new StreamWriter(fs) { AutoFlush = true };
  43. ConOut = writer;
  44. Console.SetOut(writer);
  45. Console.SetError(writer);
  46. var handle = GetStdHandle(-11); // get stdout handle (should be CONOUT$ at this point)
  47. if (GetConsoleMode(handle, out var mode))
  48. {
  49. mode |= EnableVTProcessing;
  50. if (!SetConsoleMode(handle, mode))
  51. {
  52. UseVTEscapes = false;
  53. Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
  54. new Win32Exception(Marshal.GetLastWin32Error()).Message);
  55. }
  56. }
  57. else
  58. {
  59. UseVTEscapes = false;
  60. Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
  61. new Win32Exception(Marshal.GetLastWin32Error()).Message);
  62. }
  63. }
  64. }
  65. private static void InitializeInStream()
  66. {
  67. var fs = CreateFileStream("CONIN$", GenericRead, FileShareRead, FileAccess.Read, out inHandle);
  68. if (fs != null)
  69. {
  70. Console.SetIn(ConIn = new StreamReader(fs));
  71. }
  72. }
  73. private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
  74. FileAccess dotNetFileAccess, out SafeFileHandle handle)
  75. {
  76. var file = new SafeFileHandle(CreateFile(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true);
  77. if (!file.IsInvalid)
  78. {
  79. handle = file;
  80. var fs = new FileStream(file, dotNetFileAccess);
  81. return fs;
  82. }
  83. handle = null;
  84. return null;
  85. }
  86. #region Win API Functions and Constants
  87. [DllImport("kernel32.dll")]
  88. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  89. private static extern bool AllocConsole();
  90. [DllImport("kernel32.dll")]
  91. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  92. private static extern bool AttachConsole(int dwProcessId);
  93. [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
  94. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  95. private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
  96. IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
  97. [DllImport("kernel32.dll", SetLastError = true)]
  98. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  99. private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
  100. [DllImport("kernel32.dll", SetLastError = true)]
  101. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  102. private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
  103. [DllImport("kernel32.dll")]
  104. [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
  105. private static extern IntPtr GetStdHandle(int nStdHandle);
  106. private const uint EnableVTProcessing = 0x0004;
  107. private const uint GenericWrite = 0x40000000;
  108. private const uint GenericRead = 0x80000000;
  109. private const uint FileShareRead = 0x00000001;
  110. private const uint FileShareWrite = 0x00000002;
  111. private const uint OpenExisting = 0x00000003;
  112. private const uint FileAttributeNormal = 0x80;
  113. internal const int AttachParent = -1;
  114. #endregion
  115. }
  116. }