Decompiled source of ResoniteBetterIMESupport v3.0.5

Renderer/BepInEx/plugins/blhsrwznrghfzpr-ResoniteBetterIMESupport/ResoniteBetterIMESupport.Renderer.dll

Decompiled 2 weeks ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using InterprocessLib;
using Microsoft.CodeAnalysis;
using Renderite.Shared;
using ResoniteBetterIMESupport.Shared;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("blhsrwznrghfzpr")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("3.0.5.0")]
[assembly: AssemblyInformationalVersion("3.0.5+f44c2dc2e57068838c00482e416693c9da2d9b35")]
[assembly: AssemblyProduct("ResoniteBetterIMESupport.Renderer")]
[assembly: AssemblyTitle("ResoniteBetterIMESupport.Renderer")]
[assembly: AssemblyVersion("3.0.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ResoniteBetterIMESupport.Shared
{
	internal static class ImeInterprocessChannel
	{
		public const string OwnerId = "ResoniteBetterIMESupport";

		public const string QueueName = "ResoniteBetterIMESupport.Ime";

		public const string MessageId = "ImeComposition";
	}
	internal sealed class ImeInterprocessMessage : IMemoryPackable
	{
		public string Composition = string.Empty;

		public int CompositionCursor = -1;

		public bool HasCommittedResult;

		public void Pack(ref MemoryPacker packer)
		{
			((MemoryPacker)(ref packer)).Write(Composition);
			((MemoryPacker)(ref packer)).Write<int>(CompositionCursor);
			((MemoryPacker)(ref packer)).Write<bool>(HasCommittedResult);
		}

		public void Unpack(ref MemoryUnpacker unpacker)
		{
			((MemoryUnpacker)(ref unpacker)).Read(ref Composition);
			((MemoryUnpacker)(ref unpacker)).Read<int>(ref CompositionCursor);
			((MemoryUnpacker)(ref unpacker)).Read<bool>(ref HasCommittedResult);
		}

		public override string ToString()
		{
			return $"ImeInterprocessMessage:CompositionLength={Composition.Length},CompositionCursor={CompositionCursor},HasCommittedResult={HasCommittedResult}";
		}
	}
	internal static class ImePluginConfig
	{
		private const string DebugSection = "Debug";

		private const string EnableDebugLoggingKey = "EnableDebugLogging";

		private const string EnableDebugLoggingDescription = "Enable verbose IME debug logging.";

		public static ConfigEntry<bool> BindEnableDebugLogging(ConfigFile config)
		{
			return config.Bind<bool>("Debug", "EnableDebugLogging", false, "Enable verbose IME debug logging.");
		}
	}
}
namespace ResoniteBetterIMESupport.Renderer
{
	internal static class KeyboardDriverIMEPatch
	{
		public sealed class DriverState
		{
			public string ImeComposition = string.Empty;

			public bool KeyboardInputActive;

			public Action<IMECompositionString>? CompositionHandler;
		}

		private static readonly ConditionalWeakTable<object, DriverState> States = new ConditionalWeakTable<object, DriverState>();

		private static bool _messengerIdentityLogged;

		private static Messenger? _messenger;

		public static Type KeyboardDriverType => AccessTools.TypeByName("KeyboardDriver") ?? throw new InvalidOperationException("KeyboardDriver type was not found.");

		public static DriverState GetState(object driver)
		{
			return States.GetOrCreateValue(driver);
		}

		public static void InitializeMessaging()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			if (_messenger == null)
			{
				_messenger = new Messenger("ResoniteBetterIMESupport", false, "ResoniteBetterIMESupport.Ime", (IMemoryPackerEntityPool)null, 1048576L);
				LogMessengerIdentityOnce();
			}
		}

		public static void DisposeMessaging()
		{
			Messenger? messenger = _messenger;
			if (messenger != null)
			{
				messenger.Dispose();
			}
			_messenger = null;
		}

		public static void SyncConfigEntry<T>(ConfigEntry<T> configEntry) where T : unmanaged
		{
			InitializeMessaging();
			BepInExExtensions.SyncConfigEntry<T>(_messenger, configEntry);
		}

		public static void Subscribe(object driver)
		{
			object driver2 = driver;
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				RendererPlugin.Logger.LogWarning((object)"Keyboard.current is null. IME composition support was not attached.");
				return;
			}
			DriverState state = GetState(driver2);
			if (state.CompositionHandler == null)
			{
				InitializeMessaging();
				state.CompositionHandler = delegate(IMECompositionString composition)
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					OnIMECompositionChange(driver2, composition);
				};
				current.onIMECompositionChange += state.CompositionHandler;
			}
		}

		public static void HandleKeyboardInputActive(object driver, bool keyboardInputActive)
		{
			DriverState state = GetState(driver);
			bool keyboardInputActive2 = state.KeyboardInputActive;
			state.KeyboardInputActive = keyboardInputActive;
			if (keyboardInputActive)
			{
				if (!keyboardInputActive2)
				{
					DebugLog("Keyboard input became active. Reinitializing IME state.");
					ClearComposition(state);
				}
				return;
			}
			if (!keyboardInputActive2 || state.ImeComposition.Length == 0)
			{
				ClearComposition(state);
				return;
			}
			DebugLog("Keyboard input became inactive. Canceling composition=\"" + EscapeForLog(state.ImeComposition) + "\"");
			if (!TrySendComposition(string.Empty, -1))
			{
				DebugLog("Composition clear send failed.");
			}
			ClearComposition(state);
		}

		public static bool ShouldAllowTextInput(object driver, char value)
		{
			DriverState state = GetState(driver);
			if (state.ImeComposition.Length == 0)
			{
				return true;
			}
			DebugLog($"Suppressed text input during composition: char=0x{(int)value:X4}, composition=\"{EscapeForLog(state.ImeComposition)}\"");
			return false;
		}

		private static void OnIMECompositionChange(object driver, IMECompositionString composition)
		{
			string text = ((object)(IMECompositionString)(ref composition)).ToString();
			DriverState state = GetState(driver);
			int compositionCursor = -1;
			bool flag = false;
			if (WindowsImeContextReader.TryGetCursorPosition(text, out int cursorPosition, out bool hasCommittedResult, out string _, out string diagnostic))
			{
				compositionCursor = cursorPosition;
			}
			flag = hasCommittedResult;
			DebugLog("OnIMECompositionChange: composition=\"" + EscapeForLog(text) + "\", previous=\"" + EscapeForLog(state.ImeComposition) + "\", windowsIme=" + diagnostic);
			if (!TrySendComposition(text, compositionCursor, flag))
			{
				DebugLog("Composition update send failed.");
				return;
			}
			state.ImeComposition = text;
			if (text.Length == 0)
			{
				ClearComposition(state);
			}
		}

		private static bool TrySendComposition(string composition, int compositionCursor, bool hasCommittedResult = false)
		{
			try
			{
				InitializeMessaging();
				_messenger.SendObject<ImeInterprocessMessage>("ImeComposition", new ImeInterprocessMessage
				{
					Composition = composition,
					CompositionCursor = compositionCursor,
					HasCommittedResult = hasCommittedResult
				});
				return true;
			}
			catch (Exception ex)
			{
				DebugLog("Interprocess send threw " + ex.GetType().Name + ": " + EscapeForLog(ex.Message));
				DisposeMessaging();
				return false;
			}
		}

		private static void LogMessengerIdentityOnce()
		{
			if (!_messengerIdentityLogged)
			{
				_messengerIdentityLogged = true;
				DebugLog("Renderer IME sender: ownerId=\"ResoniteBetterIMESupport\", messageId=\"ImeComposition\", queueName=\"ResoniteBetterIMESupport.Ime\"");
			}
		}

		private static void ClearComposition(DriverState state)
		{
			state.ImeComposition = string.Empty;
		}

		private static string EscapeForLog(string value)
		{
			return value.Replace("\\", "\\\\").Replace("\r", "\\r").Replace("\n", "\\n")
				.Replace("\t", "\\t");
		}

		private static void DebugLog(string message)
		{
			RendererPlugin.LogDebugIme(message);
		}
	}
	[BepInPlugin("dev.blhsrwznrghfzpr.ResoniteBetterIMESupport.Renderer", "ResoniteBetterIMESupport.Renderer", "3.0.5")]
	public sealed class RendererPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "dev.blhsrwznrghfzpr.ResoniteBetterIMESupport.Renderer";

		public const string PluginName = "ResoniteBetterIMESupport.Renderer";

		public const string PluginVersion = "3.0.5";

		internal static ManualLogSource Logger;

		private static ConfigEntry<bool> _enableDebugLogging;

		private void Awake()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			Logger = ((BaseUnityPlugin)this).Logger;
			_enableDebugLogging = ImePluginConfig.BindEnableDebugLogging(((BaseUnityPlugin)this).Config);
			KeyboardDriverIMEPatch.InitializeMessaging();
			KeyboardDriverIMEPatch.SyncConfigEntry<bool>(_enableDebugLogging);
			new Harmony("dev.blhsrwznrghfzpr.ResoniteBetterIMESupport.Renderer").PatchAll(Assembly.GetExecutingAssembly());
			Logger.LogInfo((object)"ResoniteBetterIMESupport.Renderer loaded.");
		}

		private void OnDestroy()
		{
			KeyboardDriverIMEPatch.DisposeMessaging();
		}

		internal static void LogDebugIme(string message)
		{
			if (_enableDebugLogging.Value)
			{
				Logger.LogInfo((object)("[IME debug] " + message));
			}
		}
	}
	internal static class WindowsImeContextReader
	{
		private const int GCS_COMPSTR = 8;

		private const int GCS_CURSORPOS = 128;

		private const int GCS_RESULTSTR = 2048;

		private const int IMM_ERROR_NODATA = -1;

		private const int IMM_ERROR_GENERAL = -2;

		public static bool TryGetCursorPosition(string unityComposition, out int cursorPosition, out bool hasCommittedResult, out string committedResult, out string diagnostic)
		{
			cursorPosition = -1;
			hasCommittedResult = false;
			committedResult = string.Empty;
			diagnostic = "unavailable";
			if (!IsWindows())
			{
				diagnostic = "not-windows";
				return false;
			}
			try
			{
				return TryGetCursorPositionFromImm32(unityComposition, out cursorPosition, out hasCommittedResult, out committedResult, out diagnostic);
			}
			catch (Exception ex)
			{
				cursorPosition = -1;
				hasCommittedResult = false;
				committedResult = string.Empty;
				diagnostic = "native-ime-unavailable, exception=" + ex.GetType().Name + ", message=\"" + EscapeForLog(ex.Message) + "\"";
				return false;
			}
		}

		private static bool TryGetCursorPositionFromImm32(string unityComposition, out int cursorPosition, out bool hasCommittedResult, out string committedResult, out string diagnostic)
		{
			cursorPosition = -1;
			hasCommittedResult = false;
			committedResult = string.Empty;
			diagnostic = "unavailable";
			IntPtr intPtr = GetActiveWindow();
			string arg = "active";
			if (intPtr == IntPtr.Zero)
			{
				intPtr = GetForegroundWindow();
				arg = "foreground";
			}
			if (intPtr == IntPtr.Zero)
			{
				diagnostic = "no-hwnd";
				return false;
			}
			IntPtr intPtr2 = ImmGetContext(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				diagnostic = $"no-himc, hwndSource={arg}, hwnd=0x{intPtr.ToInt64():X}";
				return false;
			}
			try
			{
				int status;
				string text = TryGetCompositionString(intPtr2, 8, out status);
				int status2;
				string text2 = TryGetCompositionString(intPtr2, 2048, out status2);
				committedResult = text2 ?? string.Empty;
				hasCommittedResult = committedResult.Length > 0;
				int num = ImmGetCompositionStringW(intPtr2, 128, IntPtr.Zero, 0);
				if (num == -1 || num == -2)
				{
					diagnostic = string.Format("cursor={0}, compStringStatus={1}, compString=\"{2}\", resultStatus={3}, resultString=\"{4}\", hasResult={5}", FormatImmValue(num), FormatImmValue(status), EscapeForLog(text ?? "<null>"), FormatImmValue(status2), EscapeForLog(committedResult), hasCommittedResult);
					return false;
				}
				int num2 = num & 0xFFFF;
				bool flag = text == null || text == unityComposition;
				diagnostic = string.Format("cursor={0}, compStringStatus={1}, compString=\"{2}\", resultStatus={3}, resultString=\"{4}\", hasResult={5}, unityMatch={6}", num2, FormatImmValue(status), EscapeForLog(text ?? "<null>"), FormatImmValue(status2), EscapeForLog(committedResult), hasCommittedResult, flag);
				if (!flag)
				{
					return false;
				}
				cursorPosition = num2;
				return true;
			}
			finally
			{
				TryReleaseContext(intPtr, intPtr2);
			}
		}

		private static void TryReleaseContext(IntPtr hwnd, IntPtr himc)
		{
			try
			{
				ImmReleaseContext(hwnd, himc);
			}
			catch
			{
			}
		}

		private static string? TryGetCompositionString(IntPtr himc, int index, out int status)
		{
			int num = (status = ImmGetCompositionStringW(himc, index, IntPtr.Zero, 0));
			if (num <= 0)
			{
				if (num != 0)
				{
					return null;
				}
				return string.Empty;
			}
			IntPtr intPtr = Marshal.AllocHGlobal(num);
			try
			{
				int num2 = (status = ImmGetCompositionStringW(himc, index, intPtr, num));
				return (num2 > 0) ? Marshal.PtrToStringUni(intPtr, num2 / 2) : ((num2 == 0) ? string.Empty : null);
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		private static bool IsWindows()
		{
			if (Environment.OSVersion.Platform != PlatformID.Win32NT && Environment.OSVersion.Platform != 0 && Environment.OSVersion.Platform != PlatformID.Win32Windows)
			{
				return Environment.OSVersion.Platform == PlatformID.WinCE;
			}
			return true;
		}

		private static string FormatImmValue(int value)
		{
			return value switch
			{
				-1 => "IMM_ERROR_NODATA", 
				-2 => "IMM_ERROR_GENERAL", 
				_ => value.ToString(), 
			};
		}

		private static string EscapeForLog(string value)
		{
			return value.Replace("\\", "\\\\").Replace("\r", "\\r").Replace("\n", "\\n")
				.Replace("\t", "\\t");
		}

		[DllImport("user32.dll")]
		private static extern IntPtr GetActiveWindow();

		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();

		[DllImport("imm32.dll")]
		private static extern IntPtr ImmGetContext(IntPtr hWnd);

		[DllImport("imm32.dll")]
		private static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);

		[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
		private static extern int ImmGetCompositionStringW(IntPtr hIMC, int dwIndex, IntPtr lpBuf, int dwBufLen);
	}
}
namespace ResoniteBetterIMESupport.Renderer.Patches
{
	[HarmonyPatch]
	internal static class KeyboardDriverStartPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(KeyboardDriverIMEPatch.KeyboardDriverType, "Start", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			KeyboardDriverIMEPatch.Subscribe(__instance);
		}
	}
	[HarmonyPatch]
	internal static class KeyboardDriverTextInputPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(KeyboardDriverIMEPatch.KeyboardDriverType, "Current_onTextInput", (Type[])null, (Type[])null);
		}

		private static bool Prefix(object __instance, char obj)
		{
			return KeyboardDriverIMEPatch.ShouldAllowTextInput(__instance, obj);
		}
	}
	[HarmonyPatch]
	internal static class KeyboardDriverHandleOutputStatePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(KeyboardDriverIMEPatch.KeyboardDriverType, "HandleOutputState", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, OutputState output)
		{
			KeyboardDriverIMEPatch.HandleKeyboardInputActive(__instance, output.keyboardInputActive);
		}
	}
}