IME implementation

This commit is contained in:
Raymond 2021-08-11 08:57:27 -04:00
parent ee44e6885a
commit 3e3757d30c
10 changed files with 1131 additions and 2 deletions

View file

@ -8,6 +8,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Hooking;
using Dalamud.Interface;
using Dalamud.Utility;
using ImGuiNET;
using Serilog;
namespace Dalamud.Game.Gui
@ -32,6 +33,7 @@ namespace Dalamud.Game.Gui
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
private readonly Hook<HandleImmDelegate> handleImmHook;
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
private GetUIMapObjectDelegate getUIMapObject;
@ -57,6 +59,7 @@ namespace Dalamud.Game.Gui
Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}");
Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}");
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
Log.Verbose($"GetUIObject address 0x{this.address.GetUIObject.ToInt64():X}");
Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}");
@ -65,13 +68,15 @@ namespace Dalamud.Game.Gui
this.Toast = new ToastGui(scanner, dalamud);
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, this.HandleItemOutDetour);
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, this.HandleActionHoverDetour);
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, this.HandleActionOutDetour);
this.handleImmHook = new Hook<HandleImmDelegate>(this.address.HandleImm, this.HandleImmDetour);
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject);
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
@ -135,6 +140,9 @@ namespace Dalamud.Game.Gui
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
@ -494,6 +502,7 @@ namespace Dalamud.Game.Gui
this.setGlobalBgmHook.Enable();
this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable();
this.handleImmHook.Enable();
this.toggleUiHideHook.Enable();
this.handleActionHoverHook.Enable();
this.handleActionOutHook.Enable();
@ -510,6 +519,7 @@ namespace Dalamud.Game.Gui
this.setGlobalBgmHook.Dispose();
this.handleItemHoverHook.Dispose();
this.handleItemOutHook.Dispose();
this.handleImmHook.Dispose();
this.toggleUiHideHook.Dispose();
this.handleActionHoverHook.Dispose();
this.handleActionOutHook.Dispose();
@ -641,5 +651,13 @@ namespace Dalamud.Game.Gui
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
}
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
{
var result = this.handleImmHook.Original(framework, a2, a3);
return ImGui.GetIO().WantTextInput
? (char)0
: result;
}
}
}

View file

@ -52,6 +52,11 @@ namespace Dalamud.Game.Gui
/// </summary>
public IntPtr HandleActionOut { get; private set; }
/// <summary>
/// Gets the address of the native HandleImm method.
/// </summary>
public IntPtr HandleImm { get; private set; }
/// <summary>
/// Gets the address of the native GetUIObject method.
/// </summary>
@ -100,6 +105,7 @@ namespace Dalamud.Game.Gui
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");

View file

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Logging.Internal;
using ImGuiNET;
using static Dalamud.NativeFunctions;
namespace Dalamud.Game.Gui.Internal
{
/// <summary>
/// This class handles IME for non-English users.
/// </summary>
internal class DalamudIME : IDisposable
{
private static readonly ModuleLog Log = new("IME");
private readonly Dalamud dalamud;
private IntPtr interfaceHandle;
private IntPtr wndProcPtr;
private IntPtr oldWndProcPtr;
private WndProcDelegate wndProcDelegate;
/// <summary>
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
internal DalamudIME(Dalamud dalamud)
{
this.dalamud = dalamud;
}
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
/// <summary>
/// Gets a value indicating whether the module is enabled.
/// </summary>
internal bool IsEnabled { get; private set; }
/// <summary>
/// Gets the imm candidates.
/// </summary>
internal List<string> ImmCand { get; private set; } = new();
/// <summary>
/// Gets the imm component.
/// </summary>
internal string ImmComp { get; private set; } = string.Empty;
/// <inheritdoc/>
public void Dispose()
{
if (this.oldWndProcPtr != IntPtr.Zero)
{
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
this.oldWndProcPtr = IntPtr.Zero;
}
}
/// <summary>
/// Enables the IME module.
/// </summary>
internal void Enable()
{
try
{
this.wndProcDelegate = this.WndProcDetour;
this.interfaceHandle = this.dalamud.InterfaceManager.WindowHandlePtr;
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
this.IsEnabled = true;
Log.Information("Enabled!");
}
catch (Exception ex)
{
Log.Information(ex, "Enable failed");
}
}
private void ToggleWindow(bool visible)
{
if (visible)
this.dalamud.DalamudUi.OpenIMEWindow();
else
this.dalamud.DalamudUi.CloseIMEWindow();
}
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
{
try
{
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
{
var io = ImGui.GetIO();
var wmsg = (WindowsMessage)msg;
switch (wmsg)
{
case WindowsMessage.WM_IME_NOTIFY:
switch ((IMECommand)wParam)
{
case IMECommand.ChangeCandidate:
this.ToggleWindow(true);
if (hWnd == IntPtr.Zero)
return 0;
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
if (size > 0)
{
var candlistPtr = Marshal.AllocHGlobal((int)size);
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
var candlist = Marshal.PtrToStructure<CandidateList>(candlistPtr);
var pageSize = candlist.PageSize;
var candCount = candlist.Count;
if (pageSize > 0 && candCount > 1)
{
var dwOffsets = new int[candCount];
for (var i = 0; i < candCount; i++)
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
var pageStart = candlist.PageStart;
// var pageEnd = pageStart + pageSize;
var cand = new string[pageSize];
this.ImmCand.Clear();
for (var i = 0; i < pageSize; i++)
{
var offStart = dwOffsets[i + pageStart];
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
var pStrStart = candlistPtr + (int)offStart;
var pStrEnd = candlistPtr + (int)offEnd;
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
if (len > 0)
{
var candBytes = new byte[len];
Marshal.Copy(pStrStart, candBytes, 0, len);
var candStr = Encoding.Unicode.GetString(candBytes);
cand[i] = candStr;
this.ImmCand.Add(candStr);
}
}
}
Marshal.FreeHGlobal(candlistPtr);
}
break;
case IMECommand.OpenCandidate:
this.ToggleWindow(true);
this.ImmCand.Clear();
break;
case IMECommand.CloseCandidate:
this.ToggleWindow(false);
this.ImmCand.Clear();
break;
default:
break;
}
break;
case WindowsMessage.WM_IME_COMPOSITION:
if ((lParam & (long)IMEComposition.ResultStr) > 0)
{
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
var bytes = new byte[dwSize];
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
Marshal.FreeHGlobal(unmanagedPointer);
var lpstr = Encoding.Unicode.GetString(bytes);
io.AddInputCharactersUTF8(lpstr);
this.ImmComp = string.Empty;
this.ImmCand.Clear();
this.ToggleWindow(false);
}
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
{
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
var bytes = new byte[dwSize];
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
Marshal.FreeHGlobal(unmanagedPointer);
var lpstr = Encoding.Unicode.GetString(bytes);
this.ImmComp = lpstr;
if (lpstr == string.Empty)
this.ToggleWindow(false);
}
break;
default:
break;
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Prevented a crash in an IME hook");
}
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
}
}
}