Parcourir la source

Fix color support with ncurses 6.1 style terminals

Bepis il y a 4 ans
Parent
commit
aca6790c29

+ 1 - 0
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -57,6 +57,7 @@
     <Compile Include="Logger\PreloaderLogWriter.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RuntimeFixes\UnityPatches.cs" />
+    <Compile Include="RuntimeFixes\XTermFix.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\BepInEx\BepInEx.csproj">

+ 12 - 2
BepInEx.Preloader/Entrypoint.cs

@@ -3,17 +3,27 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Reflection;
+using BepInEx.Preloader.RuntimeFixes;
 
 namespace BepInEx.Preloader
 {
 	internal static class PreloaderRunner
 	{
-		public static void PreloaderMain(string[] args)
+		public static void PreloaderPreMain(string[] args)
 		{
 			string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
 
 			Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
 			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
+
+			PreloaderMain();
+		}
+
+		private static void PreloaderMain()
+		{
+			if (Preloader.ConfigApplyRuntimePatches.Value)
+				XTermFix.Apply();
+
 			Preloader.Run();
 		}
 
@@ -66,7 +76,7 @@ namespace BepInEx.Preloader
 				// In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
 				// To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
 				typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
-								  ?.GetMethod(nameof(PreloaderRunner.PreloaderMain))
+								  ?.GetMethod(nameof(PreloaderRunner.PreloaderPreMain))
 								  ?.Invoke(null, new object[] { args });
 
 				AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;

+ 2 - 2
BepInEx.Preloader/Preloader.cs

@@ -41,7 +41,7 @@ namespace BepInEx.Preloader
 				}, out var harmonyBridgeException);
 
 				Exception runtimePatchException = null;
-				if(bridgeInitialized)
+				if (bridgeInitialized)
 					Utility.TryDo(() =>
 					{
 						if (ConfigApplyRuntimePatches.Value)
@@ -256,7 +256,7 @@ namespace BepInEx.Preloader
 			".cctor",
 			"The name of the method in the specified entrypoint assembly and type to hook and load Chainloader from.");
 
-		private static readonly ConfigEntry<bool> ConfigApplyRuntimePatches = ConfigFile.CoreConfig.Bind(
+		internal static readonly ConfigEntry<bool> ConfigApplyRuntimePatches = ConfigFile.CoreConfig.Bind(
 			"Preloader", "ApplyRuntimePatches",
 			true,
 			"Enables or disables runtime patches.\nThis should always be true, unless you cannot start the game due to a Harmony related issue (such as running .NET Standard runtime) or you know what you're doing.");

+ 125 - 0
BepInEx.Preloader/RuntimeFixes/XTermFix.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+using HarmonyLib;
+using MonoMod.Utils;
+
+namespace BepInEx.Preloader.RuntimeFixes
+{
+	internal static class XTermFix
+	{
+		public static void Apply()
+		{
+			if (!PlatformHelper.Is(Platform.Linux))
+				return;
+
+			if (AccessTools.Method("System.TermInfoReader:DetermineVersion") != null)
+			{
+				// Fix has been applied officially
+				return;
+			}
+
+			var harmony = new HarmonyLib.Harmony("com.bepinex.xtermfix");
+
+			harmony.Patch(AccessTools.Method("System.TermInfoReader:ReadHeader"),
+				prefix: new HarmonyMethod(typeof(XTermFix), nameof(ReadHeaderPrefix)));
+
+			harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new []{ AccessTools.TypeByName("System.TermInfoNumbers") }),
+				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoNumbersTranspiler)));
+
+			harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new []{ AccessTools.TypeByName("System.TermInfoStrings") }),
+				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));
+
+			harmony.Patch(AccessTools.Method("System.TermInfoReader:GetStringBytes", new []{ AccessTools.TypeByName("System.TermInfoStrings") }),
+				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));
+		}
+
+		public static int intOffset;
+
+		public static int GetInt32(byte[] buffer, int offset)
+		{
+			int b1 = buffer[offset];
+			int b2 = buffer[offset + 1];
+			int b3 = buffer[offset + 2];
+			int b4 = buffer[offset + 3];
+
+			if (b1 == 255 && b2 == 255 && b3 == 255 && b4 == 255)
+				return -1;
+
+			return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
+		}
+
+		public static short GetInt16(byte[] buffer, int offset)
+		{
+			int b1 = buffer[offset];
+			int b2 = buffer[offset + 1];
+
+			if (b1 == 255 && b2 == 255)
+				return -1;
+
+			return (short)(b1 | (b2 << 8));
+		}
+
+		public static int GetInteger(byte[] buffer, int offset)
+		{
+			return intOffset == 2
+				? GetInt16(buffer, offset)
+				: GetInt32(buffer, offset);
+		}
+
+		public static void DetermineVersion(short magic)
+		{
+			if (magic == 0x11a)
+				intOffset = 2;
+			else if (magic == 0x21e)
+				intOffset = 4;
+			else
+				throw new Exception($"Unknown xterm header format: {magic}");
+		}
+
+		public static bool ReadHeaderPrefix(object __instance, byte[] buffer, ref int position, ref short ___boolSize, ref short ___numSize, ref short ___strOffsets)
+		{
+			short magic = GetInt16(buffer, position);
+			position += 2;
+			DetermineVersion(magic);
+
+			// nameSize = GetInt16(buffer, position);
+			position += 2;
+			___boolSize = GetInt16(buffer, position);
+			position += 2;
+			___numSize = GetInt16(buffer, position);
+			position += 2;
+			___strOffsets = GetInt16(buffer, position);
+			position += 2;
+			// strSize = GetInt16(buffer, position);
+			position += 2;
+
+			return false;
+		}
+
+		public static IEnumerable<CodeInstruction> GetTermInfoNumbersTranspiler(IEnumerable<CodeInstruction> instructions)
+		{
+			// This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am
+
+			var list = instructions.ToList();
+
+			list[31] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));
+			list[36] = new CodeInstruction(OpCodes.Nop);
+			list[39] = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(XTermFix), nameof(GetInteger)));
+
+			return list;
+		}
+
+		public static IEnumerable<CodeInstruction> GetTermInfoStringsTranspiler(IEnumerable<CodeInstruction> instructions)
+		{
+			// This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am
+
+			var list = instructions.ToList();
+
+			list[32] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));
+
+			return list;
+		}
+	}
+}

+ 1 - 0
BepInEx/BepInEx.csproj

@@ -68,6 +68,7 @@
     <Compile Include="Configuration\AcceptableValueRange.cs" />
     <Compile Include="Configuration\ConfigEntryBase.cs" />
     <Compile Include="Console\ConsoleManager.cs" />
+    <Compile Include="Console\Unix\ConsoleWriter.cs" />
     <Compile Include="Console\Unix\DynDllImport.cs" />
     <Compile Include="Console\Unix\UnixStream.cs" />
     <Compile Include="Console\Unix\UnixStreamHelper.cs" />

+ 5 - 2
BepInEx/Console/ConsoleManager.cs

@@ -38,8 +38,11 @@ namespace BepInEx
 				case PlatformID.Unix:
 				{
 					ConsoleActive = true;
-					var writer = new StreamWriter(UnixStreamHelper.CreateDuplicateStream(1), Console.Out.Encoding);
-					writer.AutoFlush = true;
+					
+					var duplicateStream = UnixStreamHelper.CreateDuplicateStream(1);
+					
+					var writer = ConsoleWriter.CreateConsoleStreamWriter(duplicateStream, Console.Out.Encoding, true);
+					
 					StandardOutStream = writer;
 					Console.SetOut(StandardOutStream);
 					break;

+ 19 - 0
BepInEx/Console/Unix/ConsoleWriter.cs

@@ -0,0 +1,19 @@
+using System.IO;
+using System.Reflection;
+using System.Text;
+using HarmonyLib;
+
+namespace BepInEx.Unix
+{
+	public static class ConsoleWriter
+	{
+		private static ConstructorInfo cStreamWriterConstructor = AccessTools.Constructor(AccessTools.TypeByName("System.IO.CStreamWriter"));
+		public static TextWriter CreateConsoleStreamWriter(Stream stream, Encoding encoding, bool leaveOpen)
+		{
+			var writer = (StreamWriter)cStreamWriterConstructor.Invoke(null, new object[] { stream, encoding, leaveOpen, });
+			writer.AutoFlush = true;
+
+			return TextWriter.Synchronized(writer);
+		}
+	}
+}