From 3232515a61f85fd06bf1ef8377f86ca72cb72de9 Mon Sep 17 00:00:00 2001 From: Mr-X-GTA <110748953+Mr-X-GTA@users.noreply.github.com> Date: Sun, 1 Sep 2024 23:58:01 +0200 Subject: [PATCH] Input Method Editor (#3634) --- cmake/gtav-classes.cmake | 2 +- src/gta_pointers.hpp | 4 + src/gui/components/input_text.cpp | 23 ++++ src/gui/components/input_text_with_hint.cpp | 25 ++++ src/hooks/gui/wndproc.cpp | 6 + src/pointers.cpp | 10 ++ src/util/input_method_editor.hpp | 133 ++++++++++++++++++++ 7 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/util/input_method_editor.hpp diff --git a/cmake/gtav-classes.cmake b/cmake/gtav-classes.cmake index c0125956..7ed82c43 100644 --- a/cmake/gtav-classes.cmake +++ b/cmake/gtav-classes.cmake @@ -3,7 +3,7 @@ include(FetchContent) FetchContent_Declare( gtav_classes GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git - GIT_TAG 21cbc2076b8d0ac9cbd98d05ebabadcced546f30 + GIT_TAG b98cf8d4dafbde003bfbde27707574da77c01134 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" diff --git a/src/gta_pointers.hpp b/src/gta_pointers.hpp index 2c899f79..9bc1a973 100644 --- a/src/gta_pointers.hpp +++ b/src/gta_pointers.hpp @@ -22,6 +22,7 @@ class CGameScriptHandlerMgr; class CPedFactory; class GtaThread; class GameDataHash; +class InputMethodEditor; namespace rage { @@ -411,6 +412,9 @@ namespace big functions::is_ped_enemies_with m_is_ped_enemies_with; functions::can_do_damage_to_ped m_can_do_damage_to_ped; + + bool* m_allow_keyboard_layout_change; + InputMethodEditor* m_ime; }; #pragma pack(pop) static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned"); diff --git a/src/gui/components/input_text.cpp b/src/gui/components/input_text.cpp index d1895221..a17fd790 100644 --- a/src/gui/components/input_text.cpp +++ b/src/gui/components/input_text.cpp @@ -1,6 +1,7 @@ #include "fiber_pool.hpp" #include "gui/components/components.hpp" #include "misc/cpp/imgui_stdlib.h" +#include "util/input_method_editor.hpp" namespace big { @@ -15,8 +16,19 @@ namespace big } if (ImGui::IsItemActive()) + { g.self.hud.typing = TYPING_TICKS; + draw_input_method_editor(); + + *g_pointers->m_gta.m_allow_keyboard_layout_change = true; + } + + if (ImGui::IsItemDeactivated()) + { + *g_pointers->m_gta.m_allow_keyboard_layout_change = false; + } + return retval; } @@ -31,8 +43,19 @@ namespace big } if (ImGui::IsItemActive()) + { g.self.hud.typing = TYPING_TICKS; + draw_input_method_editor(); + + *g_pointers->m_gta.m_allow_keyboard_layout_change = true; + } + + if (ImGui::IsItemDeactivated()) + { + *g_pointers->m_gta.m_allow_keyboard_layout_change = false; + } + return retval; } } \ No newline at end of file diff --git a/src/gui/components/input_text_with_hint.cpp b/src/gui/components/input_text_with_hint.cpp index 4e7abbbb..5e04db33 100644 --- a/src/gui/components/input_text_with_hint.cpp +++ b/src/gui/components/input_text_with_hint.cpp @@ -1,6 +1,7 @@ #include "fiber_pool.hpp" #include "gui/components/components.hpp" #include "misc/cpp/imgui_stdlib.h" +#include "util/input_method_editor.hpp" namespace big { @@ -11,7 +12,19 @@ namespace big g_fiber_pool->queue_job(std::move(cb)); if (ImGui::IsItemActive()) + { g.self.hud.typing = TYPING_TICKS; + + draw_input_method_editor(); + + *g_pointers->m_gta.m_allow_keyboard_layout_change = true; + } + + if (ImGui::IsItemDeactivated()) + { + *g_pointers->m_gta.m_allow_keyboard_layout_change = false; + } + return returned; } @@ -22,7 +35,19 @@ namespace big g_fiber_pool->queue_job(std::move(cb)); if (ImGui::IsItemActive()) + { g.self.hud.typing = TYPING_TICKS; + + draw_input_method_editor(); + + *g_pointers->m_gta.m_allow_keyboard_layout_change = true; + } + + if (ImGui::IsItemDeactivated()) + { + *g_pointers->m_gta.m_allow_keyboard_layout_change = false; + } + return returned; } } \ No newline at end of file diff --git a/src/hooks/gui/wndproc.cpp b/src/hooks/gui/wndproc.cpp index 72931b50..d961c049 100644 --- a/src/hooks/gui/wndproc.cpp +++ b/src/hooks/gui/wndproc.cpp @@ -1,5 +1,6 @@ #include "hooking/hooking.hpp" #include "renderer/renderer.hpp" +#include "util/input_method_editor.hpp" namespace big { @@ -8,6 +9,11 @@ namespace big if (g_running) [[likely]] { g_renderer.wndproc(hwnd, msg, wparam, lparam); + + if (msg == WM_IME_COMPOSITION && lparam & GCS_RESULTSTR) [[unlikely]] + { + handle_ime_result(); + } } return CallWindowProcW(g_hooking->m_og_wndproc, hwnd, msg, wparam, lparam); diff --git a/src/pointers.cpp b/src/pointers.cpp index a88ce8cd..e141a4dd 100644 --- a/src/pointers.cpp +++ b/src/pointers.cpp @@ -1958,6 +1958,16 @@ namespace big { g_pointers->m_gta.m_can_do_damage_to_ped = ptr.add(1).rip().as(); } + }, + // Input Method Editor + { + "IME", + "75 25 89 44 24 28", + [](memory::handle ptr) + { + g_pointers->m_gta.m_allow_keyboard_layout_change = ptr.sub(4).rip().as(); + g_pointers->m_gta.m_ime = ptr.add(44).rip().sub(0x278 + 0x8).as(); + } } >(); // don't leave a trailing comma at the end diff --git a/src/util/input_method_editor.hpp b/src/util/input_method_editor.hpp new file mode 100644 index 00000000..f436b83d --- /dev/null +++ b/src/util/input_method_editor.hpp @@ -0,0 +1,133 @@ +#pragma once +#include "common.hpp" +#include "pointers.hpp" + +#include +#include + +namespace +{ + // https://github.com/ocornut/imgui/blob/864a2bf6b824f9c1329d8493386208d4b0fd311c/imgui_widgets.cpp#L3948 + static void insert_into_input_text(ImGuiInputTextState* obj, const ImWchar* new_text, int new_text_len) + { + const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int pos = obj->Stb.cursor; + const int text_len = obj->CurLenW; + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) + return; + + // Grow internal buffer if needed + if (new_text_len + text_len + 1 > obj->TextW.Size) + { + if (!is_resizable) + return; + + obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); + } + + ImWchar* text = obj->TextW.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->Edited = true; + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->TextW[obj->CurLenW] = '\0'; + + obj->Stb.cursor += new_text_len; + obj->Stb.has_preferred_x = 0; + + obj->CursorFollow = true; + } + + static void char16_to_char(char* out, const char16_t* in) + { + while (*in) + { + char16_t c = *in++; + + if (c < 0x80) + { + *out++ = static_cast(c); + } + else if (c < 0x800) + { + *out++ = static_cast(0xC0 | (c >> 6)); + *out++ = static_cast(0x80 | (c & 0x3F)); + } + else + { + *out++ = static_cast(0xE0 | (c >> 12)); + *out++ = static_cast(0x80 | ((c >> 6) & 0x3F)); + *out++ = static_cast(0x80 | (c & 0x3F)); + } + } + *out = '\0'; + } +} + +namespace big +{ + inline void handle_ime_result() + { + auto state = ImGui::GetInputTextState(ImGui::GetActiveID()); + + if (!state) + return; + + auto context = ImmGetContext(g_pointers->m_hwnd); + + ImWchar buf[31]{}; + int len = ImmGetCompositionStringW(context, GCS_RESULTSTR, buf, sizeof(buf) - 1) / sizeof(ImWchar); + + insert_into_input_text(state, buf, len); + + ImmReleaseContext(g_pointers->m_hwnd, context); + } + + inline void draw_input_method_editor() + { + if (!g_pointers->m_gta.m_ime->m_active) + return; + + constexpr size_t buf_size = ARRAYSIZE(InputMethodEditor::m_composition_string) * 3; + char buf[buf_size]; + char16_to_char(buf, g_pointers->m_gta.m_ime->m_composition_string); + + std::string text = buf; + + for (uint32_t i = 0; i < g_pointers->m_gta.m_ime->m_count; ++i) + { + char16_to_char(buf, g_pointers->m_gta.m_ime->m_candidate_list[i]); + + text += '\n'; + text += (i == g_pointers->m_gta.m_ime->m_selected_index ? '>' : ' '); + text += std::to_string(i + 1); + text += '\t'; + text += buf; + } + + constexpr float pd = 7.5f; // padding + constexpr float lt = 1.f; // line thickness + + ImVec2 ts = ImGui::CalcTextSize(text.c_str()); + + ImVec2 bl = ImGui::GetItemRectMin(); // bottom-left + ImVec2 br = {bl.x + ts.x + 2 * pd, bl.y}; // bottom-right + ImVec2 tl = {bl.x, bl.y - ts.y - 2 * pd}; // top-left + ImVec2 tr = {br.x, tl.y}; // top-right + + auto dl = ImGui::GetForegroundDrawList(); + + dl->AddRectFilled(tl, br, g.window.background_color | IM_COL32_A_MASK); + dl->AddText({tl.x + pd, tl.y + pd}, g.window.text_color, text.c_str()); + + dl->AddLine(tl, tr, IM_COL32_BLACK); // top + dl->AddLine({bl.x, bl.y - lt}, {br.x, br.y - lt}, IM_COL32_BLACK); // bottom + dl->AddLine(tl, bl, IM_COL32_BLACK); // left + dl->AddLine({tr.x - lt, tr.y}, {br.x - lt, br.y}, IM_COL32_BLACK); // right + } +} \ No newline at end of file