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);
}
}
}