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.

161 lines
6.1 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(bool alwaysCreateNewConsole = false)
  20. {
  21. bool consoleAttached = true;
  22. if (alwaysCreateNewConsole
  23. || (AttachConsole(AttachParent) == 0
  24. && Marshal.GetLastWin32Error() != ErrorAccessDenied))
  25. {
  26. consoleAttached = AllocConsole() != 0;
  27. }
  28. if (consoleAttached)
  29. {
  30. InitializeStreams();
  31. IsInitialized = true;
  32. }
  33. }
  34. public static void InitializeStreams()
  35. {
  36. InitializeOutStream();
  37. InitializeInStream();
  38. }
  39. private static void InitializeOutStream()
  40. {
  41. var fs = CreateFileStream("CONOUT$", GenericWrite, FileShareWrite, FileAccess.Write, out outHandle);
  42. if (fs != null)
  43. {
  44. var writer = new StreamWriter(fs) { AutoFlush = true };
  45. ConOut = writer;
  46. Console.SetOut(writer);
  47. Console.SetError(writer);
  48. var handle = GetStdHandle(-11); // get stdout handle (should be CONOUT$ at this point)
  49. if (GetConsoleMode(handle, out var mode))
  50. {
  51. mode |= EnableVTProcessing;
  52. if (!SetConsoleMode(handle, mode))
  53. {
  54. UseVTEscapes = false;
  55. Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
  56. new Win32Exception(Marshal.GetLastWin32Error()).Message);
  57. }
  58. }
  59. else
  60. {
  61. UseVTEscapes = false;
  62. Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
  63. new Win32Exception(Marshal.GetLastWin32Error()).Message);
  64. }
  65. }
  66. }
  67. private static void InitializeInStream()
  68. {
  69. var fs = CreateFileStream("CONIN$", GenericRead, FileShareRead, FileAccess.Read, out inHandle);
  70. if (fs != null)
  71. {
  72. Console.SetIn(ConIn = new StreamReader(fs));
  73. }
  74. }
  75. private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
  76. FileAccess dotNetFileAccess, out SafeFileHandle handle)
  77. {
  78. var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true);
  79. if (!file.IsInvalid)
  80. {
  81. handle = file;
  82. #if NET4
  83. var fs = new FileStream(file, dotNetFileAccess);
  84. #elif NET3
  85. #pragma warning disable CS0618
  86. // this is marked obsolete, and shouldn't need to be used, but the constructor used in .NET 4 doesn't exist in Unity's mscorlib.dll
  87. var fs = new FileStream(file.DangerousGetHandle(), dotNetFileAccess);
  88. #pragma warning restore
  89. #endif
  90. return fs;
  91. }
  92. handle = null;
  93. return null;
  94. }
  95. #region Win API Functions and Constants
  96. [DllImport("kernel32.dll",
  97. EntryPoint = "AllocConsole",
  98. SetLastError = true,
  99. CharSet = CharSet.Auto,
  100. CallingConvention = CallingConvention.StdCall)]
  101. private static extern int AllocConsole();
  102. [DllImport("kernel32.dll",
  103. EntryPoint = "AttachConsole",
  104. SetLastError = true,
  105. CharSet = CharSet.Auto,
  106. CallingConvention = CallingConvention.StdCall)]
  107. private static extern uint AttachConsole(uint dwProcessId);
  108. [DllImport("kernel32.dll",
  109. EntryPoint = "CreateFileW",
  110. SetLastError = true,
  111. CharSet = CharSet.Unicode,
  112. CallingConvention = CallingConvention.StdCall)]
  113. private static extern IntPtr CreateFileW(
  114. string lpFileName,
  115. uint dwDesiredAccess,
  116. uint dwShareMode,
  117. IntPtr lpSecurityAttributes,
  118. uint dwCreationDisposition,
  119. uint dwFlagsAndAttributes,
  120. IntPtr hTemplateFile
  121. );
  122. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  123. private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
  124. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  125. private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
  126. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  127. private static extern IntPtr GetStdHandle(int nStdHandle);
  128. private const uint EnableVTProcessing = 0x0004;
  129. private const uint GenericWrite = 0x40000000;
  130. private const uint GenericRead = 0x80000000;
  131. private const uint FileShareRead = 0x00000001;
  132. private const uint FileShareWrite = 0x00000002;
  133. private const uint OpenExisting = 0x00000003;
  134. private const uint FileAttributeNormal = 0x80;
  135. private const uint ErrorAccessDenied = 5;
  136. private const uint AttachParent = 0xFFFFFFFF;
  137. #endregion
  138. }
  139. }