using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using HarmonyLib; using MonoMod.RuntimeDetour; using MonoMod.RuntimeDetour.Platforms; using MonoMod.Utils; namespace BepInEx.Preloader.RuntimeFixes { internal static class XTermFix { public static void Apply() { if (PlatformHelper.Is(Platform.Windows)) return; if (typeof(Console).Assembly.GetType("System.ConsoleDriver") == null) { // Mono version is too old, use our own TTY implementation instead return; } if (AccessTools.Method("System.TermInfoReader:DetermineVersion") != null) { // Fix has been applied officially return; } // Apparently on older Unity versions (4.x), using Process.Start can run Console..cctor // And since MonoMod's PlatformHelper (used by DetourHelper.Native) runs Process.Start to determine ARM/x86 platform, // this causes a crash owing to TermInfoReader running before it can be patched and fixed // Because Doorstop does not support ARM at the moment, we can get away with just forcing x86 detour platform. // TODO: Figure out a way to detect ARM on Unix without running Process.Start DetourHelper.Native = new DetourNativeX86Platform(); 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))); DetourHelper.Native = null; } 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]; 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]; 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(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 GetTermInfoNumbersTranspiler(IEnumerable 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 GetTermInfoStringsTranspiler(IEnumerable 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; } } }