Browse Source

Refactor ConsoleManager

Bepis 4 năm trước cách đây
mục cha
commit
150a709a48

+ 4 - 1
BepInEx/BepInEx.csproj

@@ -68,10 +68,13 @@
     <Compile Include="Configuration\AcceptableValueRange.cs" />
     <Compile Include="Configuration\ConfigEntryBase.cs" />
     <Compile Include="Console\ConsoleManager.cs" />
+    <Compile Include="Console\IConsoleDriver.cs" />
     <Compile Include="Console\Unix\ConsoleWriter.cs" />
     <Compile Include="Console\Unix\DynDllImport.cs" />
+    <Compile Include="Console\Unix\LinuxConsoleDriver.cs" />
     <Compile Include="Console\Unix\UnixStream.cs" />
     <Compile Include="Console\Unix\UnixStreamHelper.cs" />
+    <Compile Include="Console\Windows\WindowsConsoleDriver.cs" />
     <Compile Include="Contract\PluginInfo.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigDescription.cs" />
@@ -87,7 +90,7 @@
     <Compile Include="Console\Windows\ConsoleEncoding\ConsoleEncoding.PInvoke.cs" />
     <Compile Include="Console\Windows\ConsoleWindow.cs" />
     <Compile Include="Console\Windows\Kon.cs" />
-    <Compile Include="Console\Windows\SafeConsole.cs" />
+    <Compile Include="Console\SafeConsole.cs" />
     <Compile Include="Bootstrap\Chainloader.cs" />
     <Compile Include="Contract\BaseUnityPlugin.cs" />
     <Compile Include="Logging\DiskLogListener.cs" />

+ 44 - 103
BepInEx/Console/ConsoleManager.cs

@@ -3,56 +3,54 @@ using System.IO;
 using System.Text;
 using BepInEx.Configuration;
 using BepInEx.Unix;
-using BepInEx.ConsoleUtil;
-using HarmonyLib;
-using UnityInjector.ConsoleUtil;
 
 namespace BepInEx
 {
 	public static class ConsoleManager
 	{
+		internal static IConsoleDriver Driver { get; set; }
+
 		/// <summary>
 		/// True if an external console has been started, false otherwise.
 		/// </summary>
-		public static bool ConsoleActive { get; private set; }
+		public static bool ConsoleActive => Driver?.ConsoleActive ?? false;
 
 		/// <summary>
 		/// The stream that writes to the standard out stream of the process. Should never be null.
 		/// </summary>
-		public static TextWriter StandardOutStream { get; internal set; }
+		public static TextWriter StandardOutStream => Driver?.StandardOut;
 
 		/// <summary>
 		/// The stream that writes to an external console. Null if no such console exists
 		/// </summary>
-		public static TextWriter ConsoleStream { get; internal set; }
+		public static TextWriter ConsoleStream => Driver?.ConsoleOut;
 
 
-		public static void Initialize(bool active)
+		public static void Initialize(bool alreadyActive)
 		{
-			StandardOutStream = Console.Out;
-			Console.SetOut(StandardOutStream);
-			ConsoleActive = active;
-
 			switch (Environment.OSVersion.Platform)
 			{
 				case PlatformID.MacOSX:
 				case PlatformID.Unix:
 				{
-					ConsoleActive = true;
-
-					var duplicateStream = UnixStreamHelper.CreateDuplicateStream(1);
-
-					var writer = ConsoleWriter.CreateConsoleStreamWriter(duplicateStream, Console.Out.Encoding, true);
-					
-					StandardOutStream = TextWriter.Synchronized(writer);
-					
-					var driver = AccessTools.Field(AccessTools.TypeByName("System.ConsoleDriver"), "driver").GetValue(null);
-					AccessTools.Field(AccessTools.TypeByName("System.TermInfoDriver"), "stdout").SetValue(driver, writer);
+					Driver = new LinuxConsoleDriver();
+					break;
+				}
 
-					Console.SetOut(StandardOutStream);
+				case PlatformID.Win32NT:
+				{
+					Driver = new WindowsConsoleDriver();
 					break;
 				}
 			}
+
+			Driver.Initialize(alreadyActive);
+		}
+
+		private static void DriverCheck()
+		{
+			if (Driver == null)
+				throw new InvalidOperationException("Driver has not been initialized");
 		}
 
 
@@ -60,22 +58,11 @@ namespace BepInEx
 		{
 			if (ConsoleActive)
 				return;
-			
-			switch (Environment.OSVersion.Platform)
-			{
-				case PlatformID.Win32NT:
-				{
-					ConsoleWindow.Attach();
-					break;
-				}
 
-				default:
-					throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
-			}
+			DriverCheck();
 
+			Driver.CreateConsole();
 			SetConsoleStreams();
-
-			ConsoleActive = true;
 		}
 
 		public static void DetachConsole()
@@ -83,103 +70,57 @@ namespace BepInEx
 			if (!ConsoleActive)
 				return;
 
-			switch (Environment.OSVersion.Platform)
-			{
-				case PlatformID.Win32NT:
-				{
-					ConsoleWindow.Detach();
-					break;
-				}
-
-				default:
-					throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
-			}
+			DriverCheck();
 
-			ConsoleActive = false;
+			Driver.DetachConsole();
+			SetConsoleStreams();
 		}
 
 		public static void SetConsoleEncoding()
 		{
-			uint encoding = ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage;
+			// Apparently Windows code-pages work in Mono.
+			// https://stackoverflow.com/a/33456543
+			// Alternatively we can pass in "shift-jis"
+			var encoding = ConfigConsoleShiftJis.Value ? Encoding.GetEncoding(932): Encoding.UTF8;
 
 			SetConsoleEncoding(encoding);
 		}
 
-		public static void SetConsoleEncoding(uint encodingCodePage)
+		public static void SetConsoleEncoding(Encoding encoding)
 		{
 			if (!ConsoleActive)
 				throw new InvalidOperationException("Console is not currently active");
 
-			switch (Environment.OSVersion.Platform)
-			{
-				case PlatformID.Win32NT:
-				{
-					ConsoleEncoding.ConsoleCodePage = encodingCodePage;
-					Console.OutputEncoding = Encoding.GetEncoding((int)encodingCodePage);
-					break;
-				}
+			DriverCheck();
 
-				case PlatformID.MacOSX:
-				case PlatformID.Unix:
-				{
-					break;
-				}
-			}
+			Driver.SetConsoleEncoding(encoding);
 		}
 
 		public static void SetConsoleTitle(string title)
 		{
-			switch (Environment.OSVersion.Platform)
-			{
-				case PlatformID.Win32NT:
-				{
-					if (!ConsoleActive)
-						return;
+			DriverCheck();
 
-					ConsoleWindow.Title = title;
-					break;
-				}
-			}
+			Driver.SetConsoleTitle(title);
 		}
 
 		public static void SetConsoleColor(ConsoleColor color)
 		{
-			switch (Environment.OSVersion.Platform)
-			{
-				case PlatformID.Win32NT:
-				{
-					if (!ConsoleActive)
-						return;
+			DriverCheck();
 
-					Kon.ForegroundColor = color;
-					break;
-				}
-			}
-
-			SafeConsole.ForegroundColor = color;
+			Driver.SetConsoleColor(color);
 		}
 
 		internal static void SetConsoleStreams()
 		{
-			switch (Environment.OSVersion.Platform)
+			if (ConsoleActive)
 			{
-				case PlatformID.Win32NT:
-				{
-					Console.SetOut(ConsoleStream);
-					Console.SetError(ConsoleStream);
-					break;
-				}
-
-				case PlatformID.MacOSX:
-				case PlatformID.Unix:
-				{
-					// We do not have external consoles on Unix platforms.
-					// Set the console output to standard output
-
-					Console.SetOut(StandardOutStream);
-					Console.SetError(StandardOutStream);
-					break;
-				}
+				Console.SetOut(ConsoleStream);
+				Console.SetError(ConsoleStream);
+			}
+			else
+			{
+				Console.SetOut(TextWriter.Null);
+				Console.SetError(TextWriter.Null);
 			}
 		}
 

+ 24 - 0
BepInEx/Console/IConsoleDriver.cs

@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace BepInEx
+{
+	internal interface IConsoleDriver
+	{
+		TextWriter StandardOut { get; }
+		TextWriter ConsoleOut { get; }
+
+		bool ConsoleActive { get; }
+		bool ConsoleIsExternal { get; }
+
+		void Initialize(bool alreadyActive);
+
+		void CreateConsole();
+		void DetachConsole();
+
+		void SetConsoleColor(ConsoleColor color);
+		void SetConsoleEncoding(Encoding encoding);
+		void SetConsoleTitle(string title);
+	}
+}

+ 11 - 4
BepInEx/Console/Windows/SafeConsole.cs

@@ -18,16 +18,20 @@ namespace UnityInjector.ConsoleUtil
 		private static SetColorDelegate _setBackgroundColor;
 		private static SetColorDelegate _setForegroundColor;
 
+		public static bool BackgroundColorExists { get; private set; }
+
 		public static ConsoleColor BackgroundColor
 		{
-			get { return _getBackgroundColor(); }
-			set { _setBackgroundColor(value); }
+			get => _getBackgroundColor();
+			set => _setBackgroundColor(value);
 		}
 
+		public static bool ForegroundColorExists { get; private set; }
+
 		public static ConsoleColor ForegroundColor
 		{
-			get { return _getForegroundColor(); }
-			set { _setForegroundColor(value); }
+			get => _getForegroundColor();
+			set => _setForegroundColor(value);
 		}
 
 		static SafeConsole()
@@ -60,6 +64,9 @@ namespace UnityInjector.ConsoleUtil
 			_getBackgroundColor = gbc != null
 				? (GetColorDelegate)Delegate.CreateDelegate(typeof(GetColorDelegate), gbc)
 				: (() => ConsoleColor.Black);
+
+			BackgroundColorExists = _setBackgroundColor != null && _getBackgroundColor != null;
+			ForegroundColorExists = _setForegroundColor != null && _getForegroundColor != null;
 		}
 
 		private delegate ConsoleColor GetColorDelegate();

+ 71 - 0
BepInEx/Console/Unix/LinuxConsoleDriver.cs

@@ -0,0 +1,71 @@
+using System;
+using System.IO;
+using System.Text;
+using BepInEx.Logging;
+using HarmonyLib;
+using UnityInjector.ConsoleUtil;
+
+namespace BepInEx.Unix
+{
+	internal class LinuxConsoleDriver : IConsoleDriver
+	{
+		public TextWriter StandardOut { get; private set; }
+		public TextWriter ConsoleOut { get; private set;  }
+
+		public bool ConsoleActive { get; private set; }
+		public bool ConsoleIsExternal => false;
+
+		public bool UseMonoTtyDriver { get; private set; }
+		public bool StdoutRedirected { get; private set; }
+
+		public void Initialize(bool alreadyActive)
+		{
+			// Console is always considered active on Unix
+			ConsoleActive = true;
+
+			var duplicateStream = UnixStreamHelper.CreateDuplicateStream(1);
+
+			var writer = ConsoleWriter.CreateConsoleStreamWriter(duplicateStream, Console.Out.Encoding, true);
+
+			StandardOut = TextWriter.Synchronized(writer);
+
+			var driver = AccessTools.Field(AccessTools.TypeByName("System.ConsoleDriver"), "driver").GetValue(null);
+			AccessTools.Field(AccessTools.TypeByName("System.TermInfoDriver"), "stdout").SetValue(driver, writer);
+
+			ConsoleOut = StandardOut;
+		}
+
+		public void CreateConsole()
+		{
+			Logger.LogWarning("An external console currently cannot be spawned on a Unix platform.");
+		}
+
+		public void DetachConsole()
+		{
+			throw new PlatformNotSupportedException("Cannot detach console on a Unix platform");
+		}
+
+		public void SetConsoleColor(ConsoleColor color)
+		{
+			if (SafeConsole.ForegroundColorExists)
+			{
+				// Use mono's inbuilt terminfo driver to set the foreground color for us
+				SafeConsole.ForegroundColor = color;
+			}
+			else
+			{
+				throw new PlatformNotSupportedException("Cannot set Unix TTY color as mono implementation is missing");
+			}
+		}
+
+		public void SetConsoleEncoding(Encoding encoding)
+		{
+			// We shouldn't be changing this on Unix
+		}
+
+		public void SetConsoleTitle(string title)
+		{
+			Console.Title = title;
+		}
+	}
+}

+ 13 - 32
BepInEx/Console/Windows/ConsoleWindow.cs

@@ -4,27 +4,23 @@
 // --------------------------------------------------
 
 using System;
-using System.IO;
 using System.Runtime.InteropServices;
-using System.Text;
-using BepInEx;
-using Microsoft.Win32.SafeHandles;
 
 namespace UnityInjector.ConsoleUtil
 {
 	internal class ConsoleWindow
 	{
 		public static bool IsAttached { get; private set; }
-		private static IntPtr _cOut;
-		private static IntPtr _oOut;
+		public static IntPtr ConsoleOutHandle;
+		public static IntPtr OriginalStdoutHandle;
 
 		public static void Attach()
 		{
 			if (IsAttached)
 				return;
 
-			if (_oOut == IntPtr.Zero)
-				_oOut = GetStdHandle(-11);
+			if (OriginalStdoutHandle == IntPtr.Zero)
+				OriginalStdoutHandle = GetStdHandle(-11);
 
 			// Store Current Window
 			IntPtr currWnd = GetForegroundWindow();
@@ -37,17 +33,12 @@ namespace UnityInjector.ConsoleUtil
 			// Restore Foreground
 			SetForegroundWindow(currWnd);
 
-			_cOut = CreateFile("CONOUT$", 0x80000000 | 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
-			BepInEx.ConsoleUtil.Kon.conOut = _cOut;
+			ConsoleOutHandle = CreateFile("CONOUT$", 0x80000000 | 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
+			BepInEx.ConsoleUtil.Kon.conOut = ConsoleOutHandle;
 
-			if (!SetStdHandle(-11, _cOut))
+			if (!SetStdHandle(-11, ConsoleOutHandle))
 				throw new Exception("SetStdHandle() failed");
 
-			var originalOutStream = new FileStream(new SafeFileHandle(_oOut, false), FileAccess.Write);
-			ConsoleManager.StandardOutStream = new StreamWriter(originalOutStream, new UTF8Encoding(false));
-
-			Init();
-
 			IsAttached = true;
 		}
 
@@ -80,14 +71,16 @@ namespace UnityInjector.ConsoleUtil
 			if (!IsAttached)
 				return;
 
-			if (!CloseHandle(_cOut))
+			if (!CloseHandle(ConsoleOutHandle))
 				throw new Exception("CloseHandle() failed");
-			_cOut = IntPtr.Zero;
+
+			ConsoleOutHandle = IntPtr.Zero;
+
 			if (!FreeConsole())
 				throw new Exception("FreeConsole() failed");
-			if (!SetStdHandle(-11, _oOut))
+
+			if (!SetStdHandle(-11, OriginalStdoutHandle))
 				throw new Exception("SetStdHandle() failed");
-			Init();
 
 			IsAttached = false;
 		}
@@ -124,18 +117,6 @@ namespace UnityInjector.ConsoleUtil
 		[DllImport("kernel32.dll", SetLastError = true)]
 		private static extern IntPtr GetStdHandle(int nStdHandle);
 
-		private static void Init()
-		{
-			var stdOut = Console.OpenStandardOutput();
-			ConsoleManager.ConsoleStream = new StreamWriter(stdOut, Encoding.Default)
-			{
-				AutoFlush = true
-			};
-
-			Console.SetOut(ConsoleManager.ConsoleStream);
-			Console.SetError(ConsoleManager.ConsoleStream);
-		}
-
 		[DllImport("kernel32.dll", SetLastError = true)]
 		private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput);
 

+ 73 - 0
BepInEx/Console/Windows/WindowsConsoleDriver.cs

@@ -0,0 +1,73 @@
+using System;
+using System.IO;
+using System.Text;
+using BepInEx.ConsoleUtil;
+using Microsoft.Win32.SafeHandles;
+using UnityInjector.ConsoleUtil;
+
+namespace BepInEx
+{
+	internal class WindowsConsoleDriver : IConsoleDriver
+	{
+		public TextWriter StandardOut { get; private set; }
+		public TextWriter ConsoleOut { get; private set; }
+
+		public bool ConsoleActive { get; private set; }
+		public bool ConsoleIsExternal => true;
+
+		public void Initialize(bool alreadyActive)
+		{
+			ConsoleActive = alreadyActive;
+
+			StandardOut = Console.Out;
+		}
+
+		public void CreateConsole()
+		{
+			ConsoleWindow.Attach();
+
+			// Not sure if this is needed? Does the original Console.Out still work?
+			var originalOutStream = new FileStream(new SafeFileHandle(ConsoleWindow.OriginalStdoutHandle, false), FileAccess.Write);
+			StandardOut = new StreamWriter(originalOutStream, new UTF8Encoding(false))
+			{
+				AutoFlush = true
+			};
+
+			var consoleOutStream = new FileStream(new SafeFileHandle(ConsoleWindow.ConsoleOutHandle, false), FileAccess.Write);
+			ConsoleOut = new StreamWriter(consoleOutStream, Console.OutputEncoding)
+			{
+				AutoFlush = true
+			};
+
+			ConsoleActive = true;
+		}
+
+		public void DetachConsole()
+		{
+			ConsoleWindow.Detach();
+
+			ConsoleOut.Close();
+			ConsoleOut = null;
+
+			ConsoleActive = false;
+		}
+
+		public void SetConsoleColor(ConsoleColor color)
+		{
+			SafeConsole.ForegroundColor = color;
+			Kon.ForegroundColor = color;
+		}
+
+		public void SetConsoleEncoding(Encoding encoding)
+		{
+			ConsoleEncoding.ConsoleCodePage = (uint)encoding.WindowsCodePage;
+			Console.OutputEncoding = encoding;
+		}
+
+		public void SetConsoleTitle(string title)
+		{
+			ConsoleWindow.Title = title;
+			Console.Title = title;
+		}
+	}
+}