From 8de385a4f1a429d3f9b800c61a0f59e6713b186d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Bogd=C4=81ns?= Date: Thu, 2 Oct 2025 10:25:58 +0300 Subject: [PATCH] IME: guard null params for CUSA04909 (#3680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changes -Added support for OrbisImeParamExtended (extended IME parameters) in ImeHandler, ImeState, and ImeUi -Updated all relevant constructors and logic to propagate and store the extended parameter - Now fully supports passing extended options from sceImeOpen to the IME UI and backend * Potential CUSA00434 [Debug] assert.cpp:30 assert_fail_debug_msg: Assertion Failed! buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?" at C:/VS/shadPS4-ime-fixes/externals/dear_imgui/imgui_widgets.cpp:4601 fix * Attempting to resolve an assertion failure in Diablo III: - Adjusted buffer sizes - Updated the calculation of text‑length values * ime-lib another hotfix Fixed incorrect param->title validation, which caused the IME dialog to fail to appear in Stardew Valley. Need to be checked. * Clang fix * FF9 ImeDialog Hotfix * Removed the validation that disallowed null text and null placeholder, since using null values is valid in `ImeDialog`. * Added additional debug logs to aid troubleshooting. * IME Fixes - Add missing flags to `OrbisImeExtOption` - Improve debug logging - Resolve nonstop `sceImeKeyboardOpen` calls in Stardew Valley (MonoGame engine) for `userId = 254` * IME: guard null params for CUSA04909 - Add null checks in IME constructors to prevent crashes seen in CUSA04909. - Leave a clear note about deferring keyboard event dispatch until guest-space translation is ready. * Some improvements - Added debug logs so every IME event and host callback (text/caret updates) shows what the guest sent back. - Updated ImeState to respect the guest’s text-length limit, keep buffers in sync, and record caret/text changes without duplicates. - Fixed shutdown by actually destroying the handler on close and letting sceImeUpdate exit quietly once the IME is gone. * CLang --------- Co-authored-by: w1naenator --- src/core/libraries/ime/ime.cpp | 153 +++++++++++++++++++++++++----- src/core/libraries/ime/ime_ui.cpp | 100 +++++++++++++++++-- src/core/libraries/ime/ime_ui.h | 1 + 3 files changed, 222 insertions(+), 32 deletions(-) diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index fde69bbf8..a2527be02 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include #include #include "common/logging/log.h" #include "core/libraries/ime/ime.h" @@ -20,11 +24,23 @@ public: ImeHandler(const OrbisImeKeyboardParam* param, Libraries::UserService::OrbisUserServiceUserId userId) : m_user_id(userId) { - LOG_INFO(Lib_Ime, "Creating ImeHandler for keyboard (user {})", userId); + LOG_DEBUG(Lib_Ime, "Creating ImeHandler for keyboard (user {})", userId); + LOG_DEBUG(Lib_Ime, "Keyboard constructor received param_ptr={:p}", + static_cast(param)); + if (!param) { + LOG_ERROR(Lib_Ime, "ImeHandler keyboard constructor received null param"); + return; + } Init(param, false); } ImeHandler(const OrbisImeParam* param, const OrbisImeParamExtended* extended = nullptr) { - LOG_INFO(Lib_Ime, "Creating ImeHandler for IME"); + LOG_DEBUG(Lib_Ime, "Creating ImeHandler for IME"); + LOG_DEBUG(Lib_Ime, "IME constructor received param_ptr={:p} extended_ptr={:p}", + static_cast(param), static_cast(extended)); + if (!param) { + LOG_ERROR(Lib_Ime, "ImeHandler IME constructor received null param"); + return; + } if (extended) { m_extended = *extended; m_has_extended = true; @@ -34,34 +50,54 @@ public: ~ImeHandler() = default; void Init(const void* param, bool ime_mode) { + LOG_DEBUG(Lib_Ime, "ImeHandler::Init begin (ime_mode={}, param_ptr={:p})", ime_mode, param); + if (ime_mode) { + LOG_DEBUG(Lib_Ime, "Copying OrbisImeParam from guest"); m_param.ime = *(OrbisImeParam*)param; } else { + LOG_DEBUG(Lib_Ime, "Copying OrbisImeKeyboardParam from guest"); m_param.key = *(OrbisImeKeyboardParam*)param; } m_ime_mode = ime_mode; + LOG_DEBUG(Lib_Ime, "Ime mode flag set to {}", ime_mode); // Open an event to let the game know the IME has started OrbisImeEvent openEvent{}; openEvent.id = (ime_mode ? OrbisImeEventId::Open : OrbisImeEventId::KeyboardOpen); + LOG_DEBUG(Lib_Ime, "Prepared initial event with id {}", static_cast(openEvent.id)); if (ime_mode) { - LOG_INFO(Lib_Ime, "calling sceImeGetPanelSize"); + LOG_DEBUG(Lib_Ime, "calling sceImeGetPanelSize"); Error e = sceImeGetPanelSize(&m_param.ime, &openEvent.param.rect.width, &openEvent.param.rect.height); if (e != Error::OK) { LOG_ERROR(Lib_Ime, "sceImeGetPanelSize returned 0x{:X}", static_cast(e)); + } else { + LOG_DEBUG(Lib_Ime, "Panel size width={} height={}", openEvent.param.rect.width, + openEvent.param.rect.height); } openEvent.param.rect.x = m_param.ime.posx; openEvent.param.rect.y = m_param.ime.posy; + LOG_DEBUG(Lib_Ime, "Panel position set to ({}, {})", openEvent.param.rect.x, + openEvent.param.rect.y); } else { + LOG_DEBUG(Lib_Ime, "Initializing keyboard handler for user {}", m_user_id); // Report the real PS4 user ID, and mark “no physical keyboard” so games know openEvent.param.resource_id_array.user_id = m_user_id; - for (auto& rid : openEvent.param.resource_id_array.resource_id) + for (auto& rid : openEvent.param.resource_id_array.resource_id) { rid = -1; - Execute(nullptr, &openEvent, /*use_param_handler=*/true); + } + LOG_DEBUG(Lib_Ime, "Reporting virtual keyboard resource ids initialized to -1"); + LOG_DEBUG(Lib_Ime, "Dispatching initial keyboard event to handler 0x{:X} (arg={:p})", + static_cast(reinterpret_cast(m_param.key.handler)), + static_cast(m_param.key.arg)); + // The guest handler expects PS4 address space; skip dispatch until we provide a proper + // translation. + LOG_WARNING(Lib_Ime, "Skipping initial keyboard event dispatch (guest handler requires " + "guest-accessible payload)"); } // Are we supposed to call the event handler on init with @@ -71,9 +107,13 @@ public: }*/ if (ime_mode) { + LOG_DEBUG(Lib_Ime, "Creating IME state and UI instances"); g_ime_state = ImeState(&m_param.ime, m_has_extended ? &m_extended : nullptr); g_ime_ui = ImeUi(&g_ime_state, &m_param.ime, m_has_extended ? &m_extended : nullptr); + LOG_DEBUG(Lib_Ime, "ImeState and ImeUi initialized"); } + + LOG_DEBUG(Lib_Ime, "ImeHandler::Init complete"); } Error Update(OrbisImeEventHandler handler) { @@ -87,6 +127,13 @@ public: while (!g_ime_state.event_queue.empty()) { OrbisImeEvent event = g_ime_state.event_queue.front(); g_ime_state.event_queue.pop(); + + const auto event_id = event.id; + const auto event_name = magic_enum::enum_name(event_id); + const char* event_name_cstr = event_name.empty() ? "Unknown" : event_name.data(); + LOG_DEBUG(Lib_Ime, "ImeHandler::Update dispatching event_id={} ({}) remaining_queue={}", + static_cast(event_id), event_name_cstr, g_ime_state.event_queue.size()); + Execute(handler, &event, false); } @@ -94,6 +141,17 @@ public: } void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) { + const auto param_arg = m_ime_mode ? static_cast(m_param.ime.arg) + : static_cast(m_param.key.arg); + const OrbisImeEventHandler target_handler = + use_param_handler ? (m_ime_mode ? m_param.ime.handler : m_param.key.handler) : handler; + LOG_DEBUG(Lib_Ime, + "Execute (ime_mode={}, use_param_handler={}, target_handler=0x{:X}, " + "param_arg={:p}, event={:p}, event_id={})", + m_ime_mode, use_param_handler, + static_cast(reinterpret_cast(target_handler)), param_arg, + static_cast(event), event ? static_cast(event->id) : -1); + if (m_ime_mode) { OrbisImeParam param = m_param.ime; if (use_param_handler) { @@ -112,11 +170,50 @@ public: } Error SetText(const char16_t* text, u32 length) { - g_ime_state.SetText(text, length); + if (!text) { + LOG_WARNING(Lib_Ime, "ImeHandler::SetText received null text pointer"); + return Error::INVALID_ADDRESS; + } + + std::size_t requested_length = length; + if (requested_length == 0) { + requested_length = std::char_traits::length(text); + } + + constexpr std::size_t kMaxOrbisLength = ORBIS_IME_MAX_TEXT_LENGTH; + const std::size_t effective_length = + std::min(requested_length, kMaxOrbisLength); + + std::array utf8_buffer{}; + std::string preview; + if (g_ime_state.ConvertOrbisToUTF8(text, effective_length, utf8_buffer.data(), + utf8_buffer.size())) { + std::string_view utf8_view{utf8_buffer.data()}; + constexpr std::size_t kPreviewLength = 64; + preview = std::string{utf8_view.substr(0, std::min(kPreviewLength, utf8_view.size()))}; + if (utf8_view.size() > kPreviewLength) { + preview += "..."; + } + } else { + preview = ""; + } + + LOG_DEBUG(Lib_Ime, "ImeHandler::SetText game feedback length={} (effective={}) preview={}", + length, effective_length, preview); + + g_ime_state.SetText(text, static_cast(effective_length)); return Error::OK; } Error SetCaret(const OrbisImeCaret* caret) { + if (!caret) { + LOG_WARNING(Lib_Ime, "ImeHandler::SetCaret received null caret pointer"); + return Error::INVALID_ADDRESS; + } + + LOG_DEBUG(Lib_Ime, "ImeHandler::SetCaret game feedback index={} pos=({}, {}) height={}", + caret->index, caret->x, caret->y, caret->height); + g_ime_state.SetCaret(caret->index); return Error::OK; } @@ -166,14 +263,14 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo() { } Error PS4_SYSV_ABI sceImeClose() { - LOG_INFO(Lib_Ime, "called"); + LOG_DEBUG(Lib_Ime, "called"); if (!g_ime_handler) { LOG_ERROR(Lib_Ime, "No IME handler is open"); return Error::NOT_OPENED; } - g_ime_handler.release(); + g_ime_handler.reset(); if (g_ime_handler) { LOG_ERROR(Lib_Ime, "Failed to close IME handler, it is still open"); return Error::INTERNAL; @@ -181,7 +278,7 @@ Error PS4_SYSV_ABI sceImeClose() { g_ime_ui = ImeUi(); g_ime_state = ImeState(); - LOG_INFO(Lib_Ime, "IME closed successfully"); + LOG_DEBUG(Lib_Ime, "IME closed successfully"); return Error::OK; } @@ -251,7 +348,7 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() { } Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { - LOG_INFO(Lib_Ime, "sceImeGetPanelSize called"); + LOG_DEBUG(Lib_Ime, "sceImeGetPanelSize called"); if (!param) { LOG_ERROR(Lib_Ime, "Invalid param: NULL"); @@ -304,12 +401,12 @@ Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u3 return Error::INVALID_TYPE; } - LOG_INFO(Lib_Ime, "IME panel size: width={}, height={}", *width, *height); + LOG_DEBUG(Lib_Ime, "IME panel size: width={}, height={}", *width, *height); return Error::OK; } Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId) { - LOG_INFO(Lib_Ime, "called"); + LOG_DEBUG(Lib_Ime, "called"); if (!g_keyboard_handler) { LOG_ERROR(Lib_Ime, "No keyboard handler is open"); @@ -328,7 +425,7 @@ Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceU return Error::INTERNAL; } - LOG_INFO(Lib_Ime, "Keyboard handler closed successfully for user ID: {}", userId); + LOG_DEBUG(Lib_Ime, "Keyboard handler closed successfully for user ID: {}", userId); return Error::OK; } @@ -339,7 +436,7 @@ int PS4_SYSV_ABI sceImeKeyboardGetInfo() { Error PS4_SYSV_ABI sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userId, OrbisImeKeyboardResourceIdArray* resourceIdArray) { - LOG_INFO(Lib_Ime, "(partial) called"); + LOG_DEBUG(Lib_Ime, "(partial) called"); if (!resourceIdArray) { LOG_ERROR(Lib_Ime, "Invalid resourceIdArray: NULL"); @@ -370,7 +467,7 @@ sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userI for (u32& id : resourceIdArray->resource_id) { id = 0; } - LOG_INFO(Lib_Ime, "No USB keyboard connected (simulated)"); + LOG_DEBUG(Lib_Ime, "No USB keyboard connected (simulated)"); return Error::CONNECTION_FAILED; // For future reference, if we had a real keyboard handler @@ -379,7 +476,7 @@ sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userI Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId, const OrbisImeKeyboardParam* param) { - LOG_INFO(Lib_Ime, "called"); + LOG_DEBUG(Lib_Ime, "called"); if (!param) { LOG_ERROR(Lib_Ime, "Invalid param: NULL"); return Error::INVALID_ADDRESS; @@ -393,7 +490,8 @@ Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUs LOG_DEBUG(Lib_Ime, " userId: {}", static_cast(userId)); LOG_DEBUG(Lib_Ime, " param->option: {:032b}", static_cast(param->option)); LOG_DEBUG(Lib_Ime, " param->arg: {}", param->arg); - LOG_DEBUG(Lib_Ime, " param->handler: {}", reinterpret_cast(param->handler)); + LOG_DEBUG(Lib_Ime, " param->handler: 0x{:X}", + static_cast(reinterpret_cast(param->handler))); // seems like arg is optional, need to check if it is used in the handler // Todo: check if arg is used in the handler, temporarily disabled @@ -442,7 +540,7 @@ Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUs LOG_ERROR(Lib_Ime, "Failed to create keyboard handler"); return Error::INTERNAL; // or Error::NO_MEMORY; } - LOG_INFO(Lib_Ime, "Keyboard handler created successfully for user ID: {}", userId); + LOG_DEBUG(Lib_Ime, "Keyboard handler created successfully for user ID: {}", userId); return Error::OK; } @@ -462,7 +560,7 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() { } Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) { - LOG_INFO(Lib_Ime, "called"); + LOG_DEBUG(Lib_Ime, "called"); if (!param) { LOG_ERROR(Lib_Ime, "Invalid param: NULL"); @@ -488,11 +586,12 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt static_cast(param->vertical_alignment)); LOG_DEBUG(Lib_Ime, "param->work: {:p}", param->work); LOG_DEBUG(Lib_Ime, "param->arg: {:p}", param->arg); - LOG_DEBUG(Lib_Ime, "param->handler: {:p}", reinterpret_cast(param->handler)); + LOG_DEBUG(Lib_Ime, " param->handler: 0x{:X}", + static_cast(reinterpret_cast(param->handler))); } if (!extended) { - LOG_INFO(Lib_Ime, "Not used extended: NULL"); + LOG_DEBUG(Lib_Ime, "Not used extended: NULL"); } else { LOG_DEBUG(Lib_Ime, "extended->option: {:032b}", static_cast(extended->option)); LOG_DEBUG(Lib_Ime, "extended->color_base: {{{},{},{},{}}}", extended->color_base.r, @@ -527,7 +626,8 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt LOG_DEBUG(Lib_Ime, "extended->ext_keyboard_mode: {}", extended->ext_keyboard_mode); } - if (param->user_id < 1 || param->user_id > 4) { // Todo: check valid user IDs + if (param->user_id == + Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { // Todo: check valid user IDs LOG_ERROR(Lib_Ime, "Invalid user_id: {}", static_cast(param->user_id)); return Error::INVALID_USER_ID; } @@ -643,7 +743,7 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt return Error::NO_MEMORY; // or Error::INTERNAL } - LOG_INFO(Lib_Ime, "IME handler created successfully"); + LOG_DEBUG(Lib_Ime, "IME handler created successfully"); return Error::OK; } @@ -653,7 +753,7 @@ int PS4_SYSV_ABI sceImeOpenInternal() { } void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) { - LOG_INFO(Lib_Ime, "sceImeParamInit called"); + LOG_DEBUG(Lib_Ime, "sceImeParamInit called"); if (!param) { return; @@ -685,9 +785,11 @@ Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) { LOG_TRACE(Lib_Ime, "called"); if (!g_ime_handler) { + LOG_ERROR(Lib_Ime, "IME handler not opened"); return Error::NOT_OPENED; } if (!text) { + LOG_ERROR(Lib_Ime, "Invalid text pointer: NULL"); return Error::INVALID_ADDRESS; } @@ -709,7 +811,8 @@ Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { } if (!g_ime_handler && !g_keyboard_handler) { - return Error::NOT_OPENED; + LOG_TRACE(Lib_Ime, "sceImeUpdate called with no active handler"); + return Error::OK; } return Error::OK; diff --git a/src/core/libraries/ime/ime_ui.cpp b/src/core/libraries/ime/ime_ui.cpp index e5ca681a7..eb97d85d4 100644 --- a/src/core/libraries/ime/ime_ui.cpp +++ b/src/core/libraries/ime/ime_ui.cpp @@ -4,6 +4,12 @@ #include "ime_ui.h" #include "imgui/imgui_std.h" +#include +#include +#include + +#include "common/logging/log.h" + namespace Libraries::Ime { using namespace ImGui; @@ -17,58 +23,124 @@ ImeState::ImeState(const OrbisImeParam* param, const OrbisImeParamExtended* exte work_buffer = param->work; text_buffer = param->inputTextBuffer; + max_text_length = param->maxTextLength; if (extended) { extended_param = *extended; has_extended = true; } - std::size_t text_len = std::char_traits::length(text_buffer); - if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), - ORBIS_IME_MAX_TEXT_LENGTH * 4)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + if (text_buffer) { + const std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } } } ImeState::ImeState(ImeState&& other) noexcept : work_buffer(other.work_buffer), text_buffer(other.text_buffer), - current_text(std::move(other.current_text)), event_queue(std::move(other.event_queue)) { + max_text_length(other.max_text_length), current_text(std::move(other.current_text)), + event_queue(std::move(other.event_queue)) { other.text_buffer = nullptr; + other.max_text_length = 0; } ImeState& ImeState::operator=(ImeState&& other) noexcept { if (this != &other) { work_buffer = other.work_buffer; text_buffer = other.text_buffer; + max_text_length = other.max_text_length; current_text = std::move(other.current_text); event_queue = std::move(other.event_queue); other.text_buffer = nullptr; + other.max_text_length = 0; } return *this; } void ImeState::SendEvent(OrbisImeEvent* event) { + if (!event) { + LOG_WARNING(Lib_Ime, "ImeState::SendEvent called with null event"); + return; + } + + const auto event_id = event->id; + const auto event_name = magic_enum::enum_name(event_id); + const char* event_name_cstr = event_name.empty() ? "Unknown" : event_name.data(); + std::unique_lock lock{queue_mutex}; event_queue.push(*event); + + LOG_DEBUG(Lib_Ime, "ImeState queued event_id={} ({}) queue_size={}", static_cast(event_id), + event_name_cstr, event_queue.size()); } void ImeState::SendEnterEvent() { + LOG_DEBUG(Lib_Ime, "ImeState::SendEnterEvent triggered"); + OrbisImeEvent enterEvent{}; enterEvent.id = OrbisImeEventId::PressEnter; SendEvent(&enterEvent); } void ImeState::SendCloseEvent() { + LOG_DEBUG(Lib_Ime, "ImeState::SendCloseEvent triggered (work_buffer={:p})", work_buffer); + OrbisImeEvent closeEvent{}; closeEvent.id = OrbisImeEventId::PressClose; closeEvent.param.text.str = reinterpret_cast(work_buffer); SendEvent(&closeEvent); } -void ImeState::SetText(const char16_t* text, u32 length) {} +void ImeState::SetText(const char16_t* text, u32 length) { + if (!text) { + LOG_WARNING(Lib_Ime, "ImeState::SetText received null text pointer"); + return; + } -void ImeState::SetCaret(u32 position) {} + std::size_t requested_length = length; + if (requested_length == 0) { + requested_length = std::char_traits::length(text); + } + + const std::size_t buffer_capacity = + max_text_length != 0 ? max_text_length : ORBIS_IME_MAX_TEXT_LENGTH; + const std::size_t effective_length = std::min(requested_length, buffer_capacity); + + if (text_buffer && buffer_capacity > 0) { + const std::size_t copy_length = std::min(effective_length, buffer_capacity); + std::copy_n(text, copy_length, text_buffer); + const std::size_t terminator_index = + copy_length < buffer_capacity ? copy_length : buffer_capacity - 1; + text_buffer[terminator_index] = u'\0'; + } + + if (!ConvertOrbisToUTF8(text, effective_length, current_text.begin(), + current_text.capacity())) { + LOG_ERROR(Lib_Ime, "ImeState::SetText failed to convert updated text to UTF-8"); + return; + } + + const auto utf8_view = current_text.to_view(); + constexpr std::size_t kPreviewLength = 64; + std::string preview = + std::string{utf8_view.substr(0, std::min(kPreviewLength, utf8_view.size()))}; + if (utf8_view.size() > kPreviewLength) { + preview += "..."; + } + + LOG_DEBUG(Lib_Ime, "ImeState::SetText stored game feedback length={} preview={}", + effective_length, preview); +} + +void ImeState::SetCaret(u32 position) { + LOG_DEBUG(Lib_Ime, + "ImeState::SetCaret stored game feedback caret_index={} current_text_length={}", + position, current_text.size()); +} bool ImeState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, std::size_t utf8_text_len) { @@ -227,6 +299,14 @@ int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { event.id = OrbisImeEventId::UpdateText; event.param.text = eventParam; + constexpr std::size_t kPreviewLength = 64; + std::string preview = currentText.substr(0, kPreviewLength); + if (currentText.size() > kPreviewLength) { + preview += "..."; + } + LOG_DEBUG(Lib_Ime, "ImeUi enqueuing UpdateText: caret_index={} length={} preview={}", + eventParam.caret_index, data->BufTextLen, preview); + lastText = currentText; ui->state->SendEvent(&event); } @@ -246,6 +326,12 @@ int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { event.id = OrbisImeEventId::UpdateCaret; event.param.caret_move = caretDirection; + const auto caret_direction_name = magic_enum::enum_name(caretDirection); + const char* caret_direction_cstr = + caret_direction_name.empty() ? "Unknown" : caret_direction_name.data(); + LOG_DEBUG(Lib_Ime, "ImeUi enqueuing UpdateCaret: caret_pos={} direction={} ({})", + data->CursorPos, static_cast(caretDirection), caret_direction_cstr); + lastCaretPos = data->CursorPos; ui->state->SendEvent(&event); } diff --git a/src/core/libraries/ime/ime_ui.h b/src/core/libraries/ime/ime_ui.h index 3e71200ed..e94ce8ea8 100644 --- a/src/core/libraries/ime/ime_ui.h +++ b/src/core/libraries/ime/ime_ui.h @@ -24,6 +24,7 @@ class ImeState { void* work_buffer{}; char16_t* text_buffer{}; + u32 max_text_length = 0; OrbisImeParamExtended extended_param{}; bool has_extended = false;