using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
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.Controls;
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.2.0")]
[assembly: AssemblyInformationalVersion("3.0.2+bb501d764477df30bdab478a08569af8258d00e0")]
[assembly: AssemblyProduct("ResoniteBetterIMESupport.Renderer")]
[assembly: AssemblyTitle("ResoniteBetterIMESupport.Renderer")]
[assembly: AssemblyVersion("3.0.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[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 ImeKeys
{
public static readonly Key[] TextEditorEditingKeys;
public static readonly Key[] RendererEditingKeys;
public static readonly Key[] CaretKeys;
static ImeKeys()
{
Key[] array = new Key[8];
RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
TextEditorEditingKeys = (Key[])(object)array;
Key[] array2 = new Key[10];
RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
RendererEditingKeys = (Key[])(object)array2;
Key[] array3 = new Key[4];
RuntimeHelpers.InitializeArray(array3, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
CaretKeys = (Key[])(object)array3;
}
}
internal static class ImeInterprocessChannel
{
public const string OwnerId = "ResoniteBetterIMESupport";
public const string MessageId = "ImeComposition";
}
internal enum ImeEditAction
{
None,
Backspace,
Delete
}
internal sealed class ImeInterprocessMessage : IMemoryPackable
{
public string Composition = string.Empty;
public string CommittedText = string.Empty;
public int CaretOffset = -1;
public ImeEditAction EditAction;
public void Pack(ref MemoryPacker packer)
{
((MemoryPacker)(ref packer)).Write(Composition);
((MemoryPacker)(ref packer)).Write(CommittedText);
((MemoryPacker)(ref packer)).Write<int>(CaretOffset);
((MemoryPacker)(ref packer)).Write<int>((int)EditAction);
}
public void Unpack(ref MemoryUnpacker unpacker)
{
((MemoryUnpacker)(ref unpacker)).Read(ref Composition);
((MemoryUnpacker)(ref unpacker)).Read(ref CommittedText);
((MemoryUnpacker)(ref unpacker)).Read<int>(ref CaretOffset);
int num = 0;
((MemoryUnpacker)(ref unpacker)).Read<int>(ref num);
EditAction = (Enum.IsDefined(typeof(ImeEditAction), num) ? ((ImeEditAction)num) : ImeEditAction.None);
}
public override string ToString()
{
return $"ImeInterprocessMessage:CompositionLength={Composition.Length},CommittedLength={CommittedText.Length},CaretOffset={CaretOffset},EditAction={EditAction}";
}
}
internal static class ImeInterprocessQueue
{
public static string GetQueueName()
{
if (TryGetArgumentValue("--bepinex-target", out string value) && !string.IsNullOrWhiteSpace(value))
{
return "ResoniteBetterIMESupport." + HashForQueue(value) + "-ResoniteBetterIMESupport";
}
if (TryGetArgumentValue("-shmprefix", out string value2) && !string.IsNullOrWhiteSpace(value2))
{
return value2 + "-ResoniteBetterIMESupport";
}
if (TryGetArgumentValue("-QueueName", out string value3) && !string.IsNullOrWhiteSpace(value3))
{
int num = value3.IndexOf('_');
return ((num > 0) ? value3.Substring(0, num) : value3) + "-ResoniteBetterIMESupport";
}
return "ResoniteBetterIMESupport.Fallback-ResoniteBetterIMESupport";
}
private static bool TryGetArgumentValue(string argumentName, out string value)
{
string[] commandLineArgs = Environment.GetCommandLineArgs();
for (int i = 0; i < commandLineArgs.Length - 1; i++)
{
if (string.Equals(commandLineArgs[i], argumentName, StringComparison.InvariantCultureIgnoreCase))
{
value = commandLineArgs[i + 1];
return true;
}
}
value = string.Empty;
return false;
}
private static string HashForQueue(string value)
{
string s = value.Trim().Replace('/', '\\').ToLowerInvariant();
using SHA256 sHA = SHA256.Create();
byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
StringBuilder stringBuilder = new StringBuilder(16);
for (int i = 0; i < 8 && i < array.Length; i++)
{
stringBuilder.Append(array[i].ToString("X2"));
}
return stringBuilder.ToString();
}
}
}
namespace ResoniteBetterIMESupport.Renderer
{
internal static class KeyboardDriverIMEPatch
{
public sealed class DriverState
{
public string ImeComposition = string.Empty;
public int CompositionCaretOffset = -1;
public int LastUpdateTypeDeltaLength = -1;
public long SuppressEmptyCompositionEndUntilTimestamp;
public bool KeyboardInputActive;
public bool IgnoreNextEmptyCompositionCommit;
public Action<IMECompositionString>? CompositionHandler;
public readonly HashSet<Key> PreviousHeldIMEEditingKeys = new HashSet<Key>();
}
private static readonly ConditionalWeakTable<object, DriverState> States = new ConditionalWeakTable<object, DriverState>();
private static readonly HashSet<object> ActiveDrivers = new HashSet<object>();
private static FieldInfo? _typeDeltaField;
private static bool _inputUpdateHooked;
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 FieldInfo TypeDeltaField
{
get
{
object obj = _typeDeltaField;
if (obj == null)
{
obj = AccessTools.Field(KeyboardDriverType, "typeDelta") ?? throw new InvalidOperationException("KeyboardDriver.typeDelta field was not found.");
_typeDeltaField = (FieldInfo?)obj;
}
return (FieldInfo)obj;
}
}
public static DriverState GetState(object driver)
{
return States.GetOrCreateValue(driver);
}
public static StringBuilder? GetTypeDelta(object driver)
{
return (StringBuilder)TypeDeltaField.GetValue(driver);
}
public static void InitializeMessaging()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
if (_messenger == null)
{
string queueName = ImeInterprocessQueue.GetQueueName();
_messenger = new Messenger("ResoniteBetterIMESupport", false, queueName, (IMemoryPackerEntityPool)null, 1048576L);
LogMessengerIdentityOnce();
}
}
public static void DisposeMessaging()
{
Messenger? messenger = _messenger;
if (messenger != null)
{
messenger.Dispose();
}
_messenger = null;
}
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;
ActiveDrivers.Add(driver2);
HookInputUpdate();
}
}
private static void HookInputUpdate()
{
if (!_inputUpdateHooked)
{
InputSystem.onAfterUpdate += OnAfterInputUpdate;
_inputUpdateHooked = true;
}
}
private static void LogMessengerIdentityOnce()
{
if (!_messengerIdentityLogged)
{
_messengerIdentityLogged = true;
DebugLog("Renderer IME InterprocessLib sender: ownerId=\"ResoniteBetterIMESupport\", messageId=\"ImeComposition\", queueName=\"" + ImeInterprocessQueue.GetQueueName() + "\"");
}
}
public static void Unsubscribe(object driver)
{
DriverState state = GetState(driver);
if (state.CompositionHandler != null)
{
Keyboard current = Keyboard.current;
if (current != null)
{
current.onIMECompositionChange -= state.CompositionHandler;
}
ActiveDrivers.Remove(driver);
state.CompositionHandler = null;
state.ImeComposition = string.Empty;
state.CompositionCaretOffset = -1;
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
state.PreviousHeldIMEEditingKeys.Clear();
}
}
public static void ClearComposition(object driver)
{
DriverState state = GetState(driver);
state.ImeComposition = string.Empty;
state.CompositionCaretOffset = -1;
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
state.PreviousHeldIMEEditingKeys.Clear();
}
public static void HandleKeyboardInputActive(object driver, bool keyboardInputActive)
{
DriverState state = GetState(driver);
bool keyboardInputActive2 = state.KeyboardInputActive;
state.KeyboardInputActive = keyboardInputActive;
if (keyboardInputActive)
{
state.IgnoreNextEmptyCompositionCommit = false;
}
else if (keyboardInputActive2)
{
CancelCompositionForInactiveKeyboard(driver);
}
}
public static void CancelCompositionForInactiveKeyboard(object driver)
{
DriverState state = GetState(driver);
if (state.ImeComposition.Length == 0)
{
ClearComposition(driver);
return;
}
DebugLog($"CancelCompositionForInactiveKeyboard: composition=\"{EscapeForLog(state.ImeComposition)}\", caretOffset={state.CompositionCaretOffset}");
if (!TrySendComposition(string.Empty, string.Empty, -1))
{
DebugLog("CancelCompositionForInactiveKeyboard InterprocessLib send failed.");
}
ClearPendingTypeDelta(driver);
ClearComposition(driver);
state.IgnoreNextEmptyCompositionCommit = true;
}
public static bool HasComposition(object driver)
{
return !string.IsNullOrEmpty(GetState(driver).ImeComposition);
}
private static void OnAfterInputUpdate()
{
Keyboard current = Keyboard.current;
if (current == null || ActiveDrivers.Count == 0)
{
return;
}
foreach (object activeDriver in ActiveDrivers)
{
SynchronizeCompositionCaret(activeDriver, current);
}
}
private static void OnIMECompositionChange(object driver, IMECompositionString composition)
{
string text = ((object)(IMECompositionString)(ref composition)).ToString();
DriverState state = GetState(driver);
StringBuilder typeDelta = GetTypeDelta(driver);
string text2 = typeDelta?.ToString() ?? string.Empty;
bool flag = IsCompositionBackspaceKeyActive();
bool flag2 = IsCompositionDeleteKeyActive();
ImeEditAction imeEditAction = GetImeEditAction(flag, flag2);
DebugLog($"OnIMECompositionChange begin: composition=\"{EscapeForLog(text)}\", committed=\"{EscapeForLog(text2)}\", previousComposition=\"{EscapeForLog(state.ImeComposition)}\", caretOffset={state.CompositionCaretOffset}, typeDeltaLength={typeDelta?.Length ?? (-1)}, backspaceKeyActive={flag}, deleteKeyActive={flag2}");
if (text.Length == 0 && state.IgnoreNextEmptyCompositionCommit)
{
DebugLog("OnIMECompositionChange ignoring empty composition commit after keyboard focus loss: committed=\"" + EscapeForLog(text2) + "\"");
state.IgnoreNextEmptyCompositionCommit = false;
ClearPendingTypeDelta(driver);
return;
}
if (text.Length > 0)
{
state.IgnoreNextEmptyCompositionCommit = false;
}
if (text.Length == 0 && state.ImeComposition.Length > 0 && IsLikelyFocusLossAccumulatedCommit(text2, state.ImeComposition))
{
DebugLog($"OnIMECompositionChange treating accumulated focus-loss TypeDelta as cancel: committedLength={text2.Length}, previousComposition=\"{EscapeForLog(state.ImeComposition)}\"");
if (!TrySendComposition(string.Empty, string.Empty, -1))
{
DebugLog("OnIMECompositionChange focus-loss cancel send failed.");
}
ClearPendingTypeDelta(driver);
ClearComposition(driver);
state.IgnoreNextEmptyCompositionCommit = true;
return;
}
if ((flag || flag2) && state.ImeComposition.Length > 1)
{
state.SuppressEmptyCompositionEndUntilTimestamp = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 10;
}
if (text.Length == 0 && state.ImeComposition.Length > 1 && IsSuppressingCompositionEndAfterDeletion(state) && (text2.Length == 0 || text2 == state.ImeComposition))
{
DebugLog("OnIMECompositionChange suppressing transient empty composition after deletion");
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
ClearPendingTypeDelta(driver);
return;
}
if (text.Length > 0)
{
state.CompositionCaretOffset = GetNextCompositionCaretOffset(state.ImeComposition, text, state.CompositionCaretOffset, flag);
}
if (TrySendComposition(text, text2, state.CompositionCaretOffset, imeEditAction))
{
DebugLog($"OnIMECompositionChange sent via InterprocessLib: composition=\"{EscapeForLog(text)}\", committed=\"{EscapeForLog(text2)}\", caretOffset={state.CompositionCaretOffset}");
ClearPendingTypeDelta(driver);
state.ImeComposition = text;
if (text.Length == 0)
{
state.CompositionCaretOffset = -1;
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
}
return;
}
DebugLog("OnIMECompositionChange InterprocessLib send failed, falling back to typeDelta.");
if (text == state.ImeComposition)
{
return;
}
if (typeDelta == null)
{
RendererPlugin.Logger.LogWarning((object)"KeyboardDriver.typeDelta is null. IME composition update was skipped.");
return;
}
string text3 = TakeTypeDeltaSuffix(driver, state.LastUpdateTypeDeltaLength);
if (text.Length == 0)
{
if (text3.Length == 0)
{
text3 = TakeFullTypeDelta(driver);
}
if (text3.Length > 0 && text3 != state.ImeComposition)
{
ReplaceComposition(typeDelta, state.ImeComposition, text3);
}
state.ImeComposition = string.Empty;
state.CompositionCaretOffset = -1;
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
}
else
{
ReplaceComposition(typeDelta, state.ImeComposition, text);
state.ImeComposition = text;
state.CompositionCaretOffset = text.Length;
}
}
private static ImeEditAction GetImeEditAction(bool backspaceKeyActive, bool deleteKeyActive)
{
if (deleteKeyActive)
{
return ImeEditAction.Delete;
}
if (backspaceKeyActive)
{
return ImeEditAction.Backspace;
}
return ImeEditAction.None;
}
private static bool IsCompositionBackspaceKeyActive()
{
Keyboard current = Keyboard.current;
if (current == null)
{
return false;
}
if (!((ButtonControl)current.backspaceKey).wasPressedThisFrame)
{
return ((ButtonControl)current.backspaceKey).isPressed;
}
return true;
}
private static bool IsCompositionDeleteKeyActive()
{
Keyboard current = Keyboard.current;
if (current == null)
{
return false;
}
if (!((ButtonControl)current.deleteKey).wasPressedThisFrame)
{
return ((ButtonControl)current.deleteKey).isPressed;
}
return true;
}
private static bool IsSuppressingCompositionEndAfterDeletion(DriverState state)
{
if (state.SuppressEmptyCompositionEndUntilTimestamp == 0L)
{
return false;
}
if (Stopwatch.GetTimestamp() <= state.SuppressEmptyCompositionEndUntilTimestamp)
{
return true;
}
state.SuppressEmptyCompositionEndUntilTimestamp = 0L;
return false;
}
private static bool IsLikelyFocusLossAccumulatedCommit(string committedText, string previousComposition)
{
if (committedText.Length == 0 || previousComposition.Length == 0)
{
return false;
}
if (committedText.Length <= Math.Max(previousComposition.Length * 2, previousComposition.Length + 8))
{
return false;
}
if (CountOccurrences(committedText, previousComposition) >= 2)
{
return true;
}
int length = Math.Min(previousComposition.Length, committedText.Length);
if (string.CompareOrdinal(committedText, 0, previousComposition, 0, length) == 0)
{
return committedText.Length > previousComposition.Length * 3;
}
return false;
}
private static int CountOccurrences(string value, string pattern)
{
int num = 0;
int num2;
for (num2 = 0; num2 < value.Length; num2 += pattern.Length)
{
num2 = value.IndexOf(pattern, num2, StringComparison.Ordinal);
if (num2 < 0)
{
return num;
}
num++;
}
return num;
}
private static int GetNextCompositionCaretOffset(string previousComposition, string nextComposition, int previousCaretOffset, bool backspaceKeyActive)
{
if (nextComposition.Length == 0)
{
return -1;
}
if (previousComposition.Length == 0 || previousCaretOffset < 0 || previousCaretOffset == previousComposition.Length)
{
return nextComposition.Length;
}
int num = TryGetBackspaceDeletionOffset(previousComposition, nextComposition, previousCaretOffset, backspaceKeyActive);
if (num >= 0)
{
return num;
}
int num2 = TryGetCaretInsertionOffset(previousComposition, nextComposition, previousCaretOffset);
if (num2 >= 0)
{
return num2;
}
int sharedPrefixLength = GetSharedPrefixLength(previousComposition, nextComposition);
int sharedSuffixLength = GetSharedSuffixLength(previousComposition, nextComposition, sharedPrefixLength);
int num3 = previousComposition.Length - sharedSuffixLength;
int val = nextComposition.Length - sharedSuffixLength;
if (previousCaretOffset < sharedPrefixLength)
{
return Math.Min(previousCaretOffset, nextComposition.Length);
}
if (previousCaretOffset <= num3)
{
return Math.Max(sharedPrefixLength, Math.Min(val, nextComposition.Length));
}
int num4 = nextComposition.Length - previousComposition.Length;
return Math.Max(0, Math.Min(previousCaretOffset + num4, nextComposition.Length));
}
private static int TryGetBackspaceDeletionOffset(string previousComposition, string nextComposition, int previousCaretOffset, bool backspaceKeyActive)
{
if (!backspaceKeyActive)
{
return -1;
}
int num = previousComposition.Length - nextComposition.Length;
if (num <= 0 || previousCaretOffset < num)
{
return -1;
}
int num2 = previousCaretOffset - num;
if (!string.Equals(previousComposition.Remove(num2, num), nextComposition, StringComparison.Ordinal))
{
return -1;
}
return num2;
}
private static int TryGetCaretInsertionOffset(string previousComposition, string nextComposition, int previousCaretOffset)
{
int num = nextComposition.Length - previousComposition.Length;
if (num <= 0)
{
return -1;
}
string value = previousComposition.Substring(0, previousCaretOffset);
string value2 = previousComposition.Substring(previousCaretOffset);
if (!nextComposition.StartsWith(value, StringComparison.Ordinal) || !nextComposition.EndsWith(value2, StringComparison.Ordinal))
{
return -1;
}
return Math.Min(previousCaretOffset + num, nextComposition.Length);
}
private static int GetSharedPrefixLength(string first, string second)
{
int num = Math.Min(first.Length, second.Length);
for (int i = 0; i < num; i++)
{
if (first[i] != second[i])
{
return i;
}
}
return num;
}
private static int GetSharedSuffixLength(string first, string second, int prefixLength)
{
int num = Math.Min(first.Length, second.Length) - prefixLength;
for (int i = 0; i < num; i++)
{
if (first[first.Length - 1 - i] != second[second.Length - 1 - i])
{
return i;
}
}
return num;
}
private static void ClearPendingTypeDelta(object driver)
{
StringBuilder typeDelta = GetTypeDelta(driver);
if (typeDelta != null && typeDelta.Length != 0)
{
typeDelta.Length = 0;
}
}
private static void ReplaceComposition(StringBuilder typeDelta, string previousComposition, string nextComposition)
{
for (int i = 0; i < previousComposition.Length; i++)
{
typeDelta.Append('\b');
}
typeDelta.Append(nextComposition);
}
public static void RemoveIMEEditingKeys(KeyboardState state)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
if (state.heldKeys != null)
{
Key[] rendererEditingKeys = ImeKeys.RendererEditingKeys;
foreach (Key item in rendererEditingKeys)
{
state.heldKeys.Remove(item);
}
}
}
public static void SynchronizeCompositionCaret(object driver, Keyboard keyboard)
{
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
DriverState state = GetState(driver);
if (state.ImeComposition.Length == 0)
{
state.PreviousHeldIMEEditingKeys.Clear();
return;
}
Key[] caretKeys = ImeKeys.CaretKeys;
foreach (Key val in caretKeys)
{
bool flag = IsIMECaretKeyPressed(val, keyboard);
bool num = IsIMECaretKeyPressedThisFrame(val, keyboard);
bool flag2 = state.PreviousHeldIMEEditingKeys.Contains(val);
if ((num || flag) && !flag2)
{
MoveCompositionCaret(driver, state, val);
}
if (flag)
{
state.PreviousHeldIMEEditingKeys.Add(val);
}
else
{
state.PreviousHeldIMEEditingKeys.Remove(val);
}
}
}
private static bool IsIMECaretKeyPressedThisFrame(Key key, Keyboard keyboard)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
KeyControl? iMECaretKeyControl = GetIMECaretKeyControl(key, keyboard);
if (iMECaretKeyControl == null)
{
return false;
}
return ((ButtonControl)iMECaretKeyControl).wasPressedThisFrame;
}
private static bool IsIMECaretKeyPressed(Key key, Keyboard keyboard)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
KeyControl? iMECaretKeyControl = GetIMECaretKeyControl(key, keyboard);
if (iMECaretKeyControl == null)
{
return false;
}
return ((ButtonControl)iMECaretKeyControl).isPressed;
}
private static KeyControl? GetIMECaretKeyControl(Key key, Keyboard keyboard)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
//IL_0004: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected I4, but got Unknown
if ((int)key != 8)
{
if ((int)key != 127)
{
return (KeyControl?)((key - 275) switch
{
1 => keyboard.leftArrowKey,
0 => keyboard.rightArrowKey,
3 => keyboard.homeKey,
4 => keyboard.endKey,
_ => null,
});
}
return keyboard.deleteKey;
}
return keyboard.backspaceKey;
}
private static void MoveCompositionCaret(object driver, DriverState state, Key key)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Invalid comparison between Unknown and I4
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Invalid comparison between Unknown and I4
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Expected I4, but got Unknown
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
int num = ((state.CompositionCaretOffset < 0) ? state.ImeComposition.Length : state.CompositionCaretOffset);
int num2 = num;
if ((int)key != 8)
{
if ((int)key == 127)
{
goto IL_0050;
}
switch (key - 275)
{
case 1:
break;
case 0:
goto IL_0050;
case 3:
num2 = 0;
goto IL_0066;
case 4:
num2 = state.ImeComposition.Length;
goto IL_0066;
default:
goto IL_0066;
}
}
num2--;
goto IL_0066;
IL_0066:
num2 = Math.Max(0, Math.Min(num2, state.ImeComposition.Length));
if (num2 == num)
{
DebugLog($"MoveCompositionCaret no-op: key={key}, composition=\"{EscapeForLog(state.ImeComposition)}\", offset={num}");
return;
}
state.CompositionCaretOffset = num2;
DebugLog($"MoveCompositionCaret send: key={key}, composition=\"{EscapeForLog(state.ImeComposition)}\", previousOffset={num}, nextOffset={num2}");
if (!TrySendComposition(state.ImeComposition, string.Empty, state.CompositionCaretOffset))
{
DebugLog("MoveCompositionCaret send failed.");
}
return;
IL_0050:
num2++;
goto IL_0066;
}
private static bool TrySendComposition(string composition, string committedText, int caretOffset, ImeEditAction editAction = ImeEditAction.None)
{
try
{
InitializeMessaging();
_messenger.SendObject<ImeInterprocessMessage>("ImeComposition", new ImeInterprocessMessage
{
Composition = composition,
CommittedText = committedText,
CaretOffset = caretOffset,
EditAction = editAction
});
return true;
}
catch (Exception ex)
{
DebugLog("InterprocessLib send threw " + ex.GetType().Name + ": " + EscapeForLog(ex.Message));
DisposeMessaging();
return false;
}
}
public static void TrimTypeDelta(object driver, int length)
{
if (length >= 0)
{
StringBuilder typeDelta = GetTypeDelta(driver);
if (typeDelta != null && typeDelta.Length > length)
{
typeDelta.Length = length;
}
}
}
private static string TakeTypeDeltaSuffix(object driver, int length)
{
if (length < 0)
{
return string.Empty;
}
StringBuilder typeDelta = GetTypeDelta(driver);
if (typeDelta == null || typeDelta.Length <= length)
{
return string.Empty;
}
string result = typeDelta.ToString(length, typeDelta.Length - length);
typeDelta.Length = length;
return result;
}
private static string TakeFullTypeDelta(object driver)
{
StringBuilder typeDelta = GetTypeDelta(driver);
if (typeDelta == null || typeDelta.Length == 0)
{
return string.Empty;
}
string result = typeDelta.ToString();
typeDelta.Length = 0;
return result;
}
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);
}
}
internal static class LegacyPluginWarning
{
public static void WarnIfLoaded(ManualLogSource logger)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
if (string.Equals(assemblies[i].GetName().Name, "NeosBetterIMESupport", StringComparison.OrdinalIgnoreCase))
{
logger.LogWarning((object)"Legacy NeosBetterIMESupport is also loaded. It attaches its own IME handler and can cause duplicated composition text. Remove or disable the old NeosBetterIMESupport plugin folder from BepInEx/plugins.");
break;
}
}
}
}
[BepInPlugin("dev.blhsrwznrghfzpr.ResoniteBetterIMESupport.Renderer", "ResoniteBetterIMESupport.Renderer", "3.0.2")]
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.2";
internal static ManualLogSource Logger;
private static ConfigEntry<bool> _enableDebugLogging;
private void Awake()
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
Logger = ((BaseUnityPlugin)this).Logger;
_enableDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugLogging", false, "Enable verbose IME debug logging.");
LegacyPluginWarning.WarnIfLoaded(Logger);
KeyboardDriverIMEPatch.InitializeMessaging();
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));
}
}
}
}
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 KeyboardDriverUpdateStatePatch
{
private static MethodBase TargetMethod()
{
return AccessTools.Method(KeyboardDriverIMEPatch.KeyboardDriverType, "UpdateState", (Type[])null, (Type[])null);
}
private static void Prefix(object __instance, out int __state)
{
__state = KeyboardDriverIMEPatch.GetTypeDelta(__instance)?.Length ?? (-1);
KeyboardDriverIMEPatch.GetState(__instance).LastUpdateTypeDeltaLength = __state;
}
private static void Postfix(object __instance, KeyboardState state, int __state)
{
if (KeyboardDriverIMEPatch.HasComposition(__instance))
{
if (__state >= 0)
{
KeyboardDriverIMEPatch.TrimTypeDelta(__instance, __state);
}
KeyboardDriverIMEPatch.RemoveIMEEditingKeys(state);
}
}
}
[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);
}
}
}