feat: Input Method Editor

This commit is contained in:
Mr-X-GTA 2024-08-29 14:29:08 +02:00
parent 6af80e936d
commit 4a2d8e207f
6 changed files with 193 additions and 0 deletions

View File

@ -22,6 +22,7 @@ class CGameScriptHandlerMgr;
class CPedFactory; class CPedFactory;
class GtaThread; class GtaThread;
class GameDataHash; class GameDataHash;
class InputMethodEditor;
namespace rage namespace rage
{ {
@ -411,6 +412,9 @@ namespace big
functions::is_ped_enemies_with m_is_ped_enemies_with; functions::is_ped_enemies_with m_is_ped_enemies_with;
functions::can_do_damage_to_ped m_can_do_damage_to_ped; functions::can_do_damage_to_ped m_can_do_damage_to_ped;
bool* m_allow_keyboard_layout_change;
InputMethodEditor* m_ime;
}; };
#pragma pack(pop) #pragma pack(pop)
static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned"); static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned");

View File

@ -1,6 +1,7 @@
#include "fiber_pool.hpp" #include "fiber_pool.hpp"
#include "gui/components/components.hpp" #include "gui/components/components.hpp"
#include "misc/cpp/imgui_stdlib.h" #include "misc/cpp/imgui_stdlib.h"
#include "util/input_method_editor.hpp"
namespace big namespace big
{ {
@ -15,8 +16,19 @@ namespace big
} }
if (ImGui::IsItemActive()) if (ImGui::IsItemActive())
{
g.self.hud.typing = TYPING_TICKS; 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; return retval;
} }
@ -31,8 +43,19 @@ namespace big
} }
if (ImGui::IsItemActive()) if (ImGui::IsItemActive())
{
g.self.hud.typing = TYPING_TICKS; 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; return retval;
} }
} }

View File

@ -1,6 +1,7 @@
#include "fiber_pool.hpp" #include "fiber_pool.hpp"
#include "gui/components/components.hpp" #include "gui/components/components.hpp"
#include "misc/cpp/imgui_stdlib.h" #include "misc/cpp/imgui_stdlib.h"
#include "util/input_method_editor.hpp"
namespace big namespace big
{ {
@ -11,7 +12,19 @@ namespace big
g_fiber_pool->queue_job(std::move(cb)); g_fiber_pool->queue_job(std::move(cb));
if (ImGui::IsItemActive()) if (ImGui::IsItemActive())
{
g.self.hud.typing = TYPING_TICKS; 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; return returned;
} }
@ -22,7 +35,19 @@ namespace big
g_fiber_pool->queue_job(std::move(cb)); g_fiber_pool->queue_job(std::move(cb));
if (ImGui::IsItemActive()) if (ImGui::IsItemActive())
{
g.self.hud.typing = TYPING_TICKS; 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; return returned;
} }
} }

View File

@ -1,5 +1,6 @@
#include "hooking/hooking.hpp" #include "hooking/hooking.hpp"
#include "renderer/renderer.hpp" #include "renderer/renderer.hpp"
#include "util/input_method_editor.hpp"
namespace big namespace big
{ {
@ -8,6 +9,11 @@ namespace big
if (g_running) [[likely]] if (g_running) [[likely]]
{ {
g_renderer.wndproc(hwnd, msg, wparam, lparam); 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); return CallWindowProcW(g_hooking->m_og_wndproc, hwnd, msg, wparam, lparam);

View File

@ -1958,6 +1958,16 @@ namespace big
{ {
g_pointers->m_gta.m_can_do_damage_to_ped = ptr.add(1).rip().as<functions::can_do_damage_to_ped>(); g_pointers->m_gta.m_can_do_damage_to_ped = ptr.add(1).rip().as<functions::can_do_damage_to_ped>();
} }
},
// 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<bool*>();
g_pointers->m_gta.m_ime = ptr.add(44).rip().sub(0x278 + 0x8).as<InputMethodEditor*>();
}
} }
>(); // don't leave a trailing comma at the end >(); // don't leave a trailing comma at the end

View File

@ -0,0 +1,125 @@
#pragma once
#include "common.hpp"
#include "pointers.hpp"
#include <imgui_internal.h>
#pragma pack(push, 8)
class InputMethodEditor
{
public:
uint32_t m_count; //0x0000
uint32_t m_selected_index; //0x0004
wchar_t m_composition_string[31]; //0x0008
wchar_t m_candidate_list[9][31]; //0x0046
bool m_active; //0x0274
char pad_0275[3]; //0x0275
}; //Size: 0x0278
static_assert(sizeof(InputMethodEditor) == 0x278);
#pragma pack(pop)
namespace
{
// https://github.com/ocornut/imgui/blob/864a2bf6b824f9c1329d8493386208d4b0fd311c/imgui_widgets.cpp#L3948
static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
{
const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
const int text_len = obj->CurLenW;
IM_ASSERT(pos <= text_len);
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 false;
// Grow internal buffer if needed
if (new_text_len + text_len + 1 > obj->TextW.Size)
{
if (!is_resizable)
return false;
IM_ASSERT(text_len < obj->TextW.Size);
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';
return true;
}
}
namespace big
{
inline void handle_ime_result()
{
auto state = ImGui::GetInputTextState(ImGui::GetActiveID());
if (!state)
return;
auto context = ImmGetContext(g_pointers->m_hwnd);
wchar_t buf[31]{};
int len = ImmGetCompositionStringW(context, GCS_RESULTSTR, buf, sizeof(buf) - 1) / 2;
if (STB_TEXTEDIT_INSERTCHARS(state, state->Stb.cursor, (ImWchar*)buf, len))
{
state->Stb.cursor += len;
state->Stb.has_preferred_x = 0;
state->CursorFollow = true;
}
ImmReleaseContext(g_pointers->m_hwnd, context);
}
inline void draw_input_method_editor()
{
if (!g_pointers->m_gta.m_ime->m_active)
return;
std::string text;
char buf[62];
ImTextStrToUtf8(buf, sizeof(buf), (ImWchar*)g_pointers->m_gta.m_ime->m_composition_string, nullptr);
text += buf;
for (uint32_t i = 0; i < g_pointers->m_gta.m_ime->m_count; ++i)
{
ImTextStrToUtf8(buf, sizeof(buf), (ImWchar*)g_pointers->m_gta.m_ime->m_candidate_list[i], nullptr);
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
}
}