IME: guard null params for CUSA04909 (#3680)

* 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] <Critical> 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 <valdis.bogdans@hotmail.com>
This commit is contained in:
Valdis Bogdāns
2025-10-02 10:25:58 +03:00
committed by GitHub
parent 110a735a04
commit 8de385a4f1
3 changed files with 222 additions and 32 deletions

View File

@@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <queue> #include <queue>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/ime/ime.h" #include "core/libraries/ime/ime.h"
@@ -20,11 +24,23 @@ public:
ImeHandler(const OrbisImeKeyboardParam* param, ImeHandler(const OrbisImeKeyboardParam* param,
Libraries::UserService::OrbisUserServiceUserId userId) Libraries::UserService::OrbisUserServiceUserId userId)
: m_user_id(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<const void*>(param));
if (!param) {
LOG_ERROR(Lib_Ime, "ImeHandler keyboard constructor received null param");
return;
}
Init(param, false); Init(param, false);
} }
ImeHandler(const OrbisImeParam* param, const OrbisImeParamExtended* extended = nullptr) { 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<const void*>(param), static_cast<const void*>(extended));
if (!param) {
LOG_ERROR(Lib_Ime, "ImeHandler IME constructor received null param");
return;
}
if (extended) { if (extended) {
m_extended = *extended; m_extended = *extended;
m_has_extended = true; m_has_extended = true;
@@ -34,34 +50,54 @@ public:
~ImeHandler() = default; ~ImeHandler() = default;
void Init(const void* param, bool ime_mode) { 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) { if (ime_mode) {
LOG_DEBUG(Lib_Ime, "Copying OrbisImeParam from guest");
m_param.ime = *(OrbisImeParam*)param; m_param.ime = *(OrbisImeParam*)param;
} else { } else {
LOG_DEBUG(Lib_Ime, "Copying OrbisImeKeyboardParam from guest");
m_param.key = *(OrbisImeKeyboardParam*)param; m_param.key = *(OrbisImeKeyboardParam*)param;
} }
m_ime_mode = ime_mode; 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 // Open an event to let the game know the IME has started
OrbisImeEvent openEvent{}; OrbisImeEvent openEvent{};
openEvent.id = (ime_mode ? OrbisImeEventId::Open : OrbisImeEventId::KeyboardOpen); openEvent.id = (ime_mode ? OrbisImeEventId::Open : OrbisImeEventId::KeyboardOpen);
LOG_DEBUG(Lib_Ime, "Prepared initial event with id {}", static_cast<u32>(openEvent.id));
if (ime_mode) { 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, Error e = sceImeGetPanelSize(&m_param.ime, &openEvent.param.rect.width,
&openEvent.param.rect.height); &openEvent.param.rect.height);
if (e != Error::OK) { if (e != Error::OK) {
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize returned 0x{:X}", static_cast<u32>(e)); LOG_ERROR(Lib_Ime, "sceImeGetPanelSize returned 0x{:X}", static_cast<u32>(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.x = m_param.ime.posx;
openEvent.param.rect.y = m_param.ime.posy; 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 { } 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 // Report the real PS4 user ID, and mark “no physical keyboard” so games know
openEvent.param.resource_id_array.user_id = m_user_id; 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; 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<u64>(reinterpret_cast<uintptr_t>(m_param.key.handler)),
static_cast<const void*>(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 // Are we supposed to call the event handler on init with
@@ -71,9 +107,13 @@ public:
}*/ }*/
if (ime_mode) { 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_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); 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) { Error Update(OrbisImeEventHandler handler) {
@@ -87,6 +127,13 @@ public:
while (!g_ime_state.event_queue.empty()) { while (!g_ime_state.event_queue.empty()) {
OrbisImeEvent event = g_ime_state.event_queue.front(); OrbisImeEvent event = g_ime_state.event_queue.front();
g_ime_state.event_queue.pop(); 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<u32>(event_id), event_name_cstr, g_ime_state.event_queue.size());
Execute(handler, &event, false); Execute(handler, &event, false);
} }
@@ -94,6 +141,17 @@ public:
} }
void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) { void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) {
const auto param_arg = m_ime_mode ? static_cast<const void*>(m_param.ime.arg)
: static_cast<const void*>(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<u64>(reinterpret_cast<uintptr_t>(target_handler)), param_arg,
static_cast<const void*>(event), event ? static_cast<int>(event->id) : -1);
if (m_ime_mode) { if (m_ime_mode) {
OrbisImeParam param = m_param.ime; OrbisImeParam param = m_param.ime;
if (use_param_handler) { if (use_param_handler) {
@@ -112,11 +170,50 @@ public:
} }
Error SetText(const char16_t* text, u32 length) { 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<char16_t>::length(text);
}
constexpr std::size_t kMaxOrbisLength = ORBIS_IME_MAX_TEXT_LENGTH;
const std::size_t effective_length =
std::min<std::size_t>(requested_length, kMaxOrbisLength);
std::array<char, ORBIS_IME_MAX_TEXT_LENGTH * 4 + 1> 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 = "<conversion failed>";
}
LOG_DEBUG(Lib_Ime, "ImeHandler::SetText game feedback length={} (effective={}) preview={}",
length, effective_length, preview);
g_ime_state.SetText(text, static_cast<u32>(effective_length));
return Error::OK; return Error::OK;
} }
Error SetCaret(const OrbisImeCaret* caret) { 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); g_ime_state.SetCaret(caret->index);
return Error::OK; return Error::OK;
} }
@@ -166,14 +263,14 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo() {
} }
Error PS4_SYSV_ABI sceImeClose() { Error PS4_SYSV_ABI sceImeClose() {
LOG_INFO(Lib_Ime, "called"); LOG_DEBUG(Lib_Ime, "called");
if (!g_ime_handler) { if (!g_ime_handler) {
LOG_ERROR(Lib_Ime, "No IME handler is open"); LOG_ERROR(Lib_Ime, "No IME handler is open");
return Error::NOT_OPENED; return Error::NOT_OPENED;
} }
g_ime_handler.release(); g_ime_handler.reset();
if (g_ime_handler) { if (g_ime_handler) {
LOG_ERROR(Lib_Ime, "Failed to close IME handler, it is still open"); LOG_ERROR(Lib_Ime, "Failed to close IME handler, it is still open");
return Error::INTERNAL; return Error::INTERNAL;
@@ -181,7 +278,7 @@ Error PS4_SYSV_ABI sceImeClose() {
g_ime_ui = ImeUi(); g_ime_ui = ImeUi();
g_ime_state = ImeState(); g_ime_state = ImeState();
LOG_INFO(Lib_Ime, "IME closed successfully"); LOG_DEBUG(Lib_Ime, "IME closed successfully");
return Error::OK; return Error::OK;
} }
@@ -251,7 +348,7 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() {
} }
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { 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) { if (!param) {
LOG_ERROR(Lib_Ime, "Invalid param: NULL"); 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; 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; return Error::OK;
} }
Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId) { Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId) {
LOG_INFO(Lib_Ime, "called"); LOG_DEBUG(Lib_Ime, "called");
if (!g_keyboard_handler) { if (!g_keyboard_handler) {
LOG_ERROR(Lib_Ime, "No keyboard handler is open"); LOG_ERROR(Lib_Ime, "No keyboard handler is open");
@@ -328,7 +425,7 @@ Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceU
return Error::INTERNAL; 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; return Error::OK;
} }
@@ -339,7 +436,7 @@ int PS4_SYSV_ABI sceImeKeyboardGetInfo() {
Error PS4_SYSV_ABI Error PS4_SYSV_ABI
sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userId, sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userId,
OrbisImeKeyboardResourceIdArray* resourceIdArray) { OrbisImeKeyboardResourceIdArray* resourceIdArray) {
LOG_INFO(Lib_Ime, "(partial) called"); LOG_DEBUG(Lib_Ime, "(partial) called");
if (!resourceIdArray) { if (!resourceIdArray) {
LOG_ERROR(Lib_Ime, "Invalid resourceIdArray: NULL"); LOG_ERROR(Lib_Ime, "Invalid resourceIdArray: NULL");
@@ -370,7 +467,7 @@ sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userI
for (u32& id : resourceIdArray->resource_id) { for (u32& id : resourceIdArray->resource_id) {
id = 0; id = 0;
} }
LOG_INFO(Lib_Ime, "No USB keyboard connected (simulated)"); LOG_DEBUG(Lib_Ime, "No USB keyboard connected (simulated)");
return Error::CONNECTION_FAILED; return Error::CONNECTION_FAILED;
// For future reference, if we had a real keyboard handler // 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, Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId,
const OrbisImeKeyboardParam* param) { const OrbisImeKeyboardParam* param) {
LOG_INFO(Lib_Ime, "called"); LOG_DEBUG(Lib_Ime, "called");
if (!param) { if (!param) {
LOG_ERROR(Lib_Ime, "Invalid param: NULL"); LOG_ERROR(Lib_Ime, "Invalid param: NULL");
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
@@ -393,7 +490,8 @@ Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUs
LOG_DEBUG(Lib_Ime, " userId: {}", static_cast<u32>(userId)); LOG_DEBUG(Lib_Ime, " userId: {}", static_cast<u32>(userId));
LOG_DEBUG(Lib_Ime, " param->option: {:032b}", static_cast<u32>(param->option)); LOG_DEBUG(Lib_Ime, " param->option: {:032b}", static_cast<u32>(param->option));
LOG_DEBUG(Lib_Ime, " param->arg: {}", param->arg); LOG_DEBUG(Lib_Ime, " param->arg: {}", param->arg);
LOG_DEBUG(Lib_Ime, " param->handler: {}", reinterpret_cast<const void*>(param->handler)); LOG_DEBUG(Lib_Ime, " param->handler: 0x{:X}",
static_cast<u64>(reinterpret_cast<uintptr_t>(param->handler)));
// seems like arg is optional, need to check if it is used in the 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 // 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"); LOG_ERROR(Lib_Ime, "Failed to create keyboard handler");
return Error::INTERNAL; // or Error::NO_MEMORY; 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; return Error::OK;
} }
@@ -462,7 +560,7 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() {
} }
Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) { Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) {
LOG_INFO(Lib_Ime, "called"); LOG_DEBUG(Lib_Ime, "called");
if (!param) { if (!param) {
LOG_ERROR(Lib_Ime, "Invalid param: NULL"); LOG_ERROR(Lib_Ime, "Invalid param: NULL");
@@ -488,11 +586,12 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
static_cast<u32>(param->vertical_alignment)); static_cast<u32>(param->vertical_alignment));
LOG_DEBUG(Lib_Ime, "param->work: {:p}", param->work); LOG_DEBUG(Lib_Ime, "param->work: {:p}", param->work);
LOG_DEBUG(Lib_Ime, "param->arg: {:p}", param->arg); LOG_DEBUG(Lib_Ime, "param->arg: {:p}", param->arg);
LOG_DEBUG(Lib_Ime, "param->handler: {:p}", reinterpret_cast<void*>(param->handler)); LOG_DEBUG(Lib_Ime, " param->handler: 0x{:X}",
static_cast<u64>(reinterpret_cast<uintptr_t>(param->handler)));
} }
if (!extended) { if (!extended) {
LOG_INFO(Lib_Ime, "Not used extended: NULL"); LOG_DEBUG(Lib_Ime, "Not used extended: NULL");
} else { } else {
LOG_DEBUG(Lib_Ime, "extended->option: {:032b}", static_cast<u32>(extended->option)); LOG_DEBUG(Lib_Ime, "extended->option: {:032b}", static_cast<u32>(extended->option));
LOG_DEBUG(Lib_Ime, "extended->color_base: {{{},{},{},{}}}", extended->color_base.r, 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); 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<u32>(param->user_id)); LOG_ERROR(Lib_Ime, "Invalid user_id: {}", static_cast<u32>(param->user_id));
return Error::INVALID_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 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; return Error::OK;
} }
@@ -653,7 +753,7 @@ int PS4_SYSV_ABI sceImeOpenInternal() {
} }
void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) { void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) {
LOG_INFO(Lib_Ime, "sceImeParamInit called"); LOG_DEBUG(Lib_Ime, "sceImeParamInit called");
if (!param) { if (!param) {
return; return;
@@ -685,9 +785,11 @@ Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) {
LOG_TRACE(Lib_Ime, "called"); LOG_TRACE(Lib_Ime, "called");
if (!g_ime_handler) { if (!g_ime_handler) {
LOG_ERROR(Lib_Ime, "IME handler not opened");
return Error::NOT_OPENED; return Error::NOT_OPENED;
} }
if (!text) { if (!text) {
LOG_ERROR(Lib_Ime, "Invalid text pointer: NULL");
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
@@ -709,7 +811,8 @@ Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) {
} }
if (!g_ime_handler && !g_keyboard_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; return Error::OK;

View File

@@ -4,6 +4,12 @@
#include "ime_ui.h" #include "ime_ui.h"
#include "imgui/imgui_std.h" #include "imgui/imgui_std.h"
#include <algorithm>
#include <string>
#include <string_view>
#include "common/logging/log.h"
namespace Libraries::Ime { namespace Libraries::Ime {
using namespace ImGui; using namespace ImGui;
@@ -17,58 +23,124 @@ ImeState::ImeState(const OrbisImeParam* param, const OrbisImeParamExtended* exte
work_buffer = param->work; work_buffer = param->work;
text_buffer = param->inputTextBuffer; text_buffer = param->inputTextBuffer;
max_text_length = param->maxTextLength;
if (extended) { if (extended) {
extended_param = *extended; extended_param = *extended;
has_extended = true; has_extended = true;
} }
std::size_t text_len = std::char_traits<char16_t>::length(text_buffer); if (text_buffer) {
const std::size_t text_len = std::char_traits<char16_t>::length(text_buffer);
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
ORBIS_IME_MAX_TEXT_LENGTH * 4)) { ORBIS_IME_MAX_TEXT_LENGTH * 4)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
} }
} }
}
ImeState::ImeState(ImeState&& other) noexcept ImeState::ImeState(ImeState&& other) noexcept
: work_buffer(other.work_buffer), text_buffer(other.text_buffer), : 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.text_buffer = nullptr;
other.max_text_length = 0;
} }
ImeState& ImeState::operator=(ImeState&& other) noexcept { ImeState& ImeState::operator=(ImeState&& other) noexcept {
if (this != &other) { if (this != &other) {
work_buffer = other.work_buffer; work_buffer = other.work_buffer;
text_buffer = other.text_buffer; text_buffer = other.text_buffer;
max_text_length = other.max_text_length;
current_text = std::move(other.current_text); current_text = std::move(other.current_text);
event_queue = std::move(other.event_queue); event_queue = std::move(other.event_queue);
other.text_buffer = nullptr; other.text_buffer = nullptr;
other.max_text_length = 0;
} }
return *this; return *this;
} }
void ImeState::SendEvent(OrbisImeEvent* event) { 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<std::mutex> lock{queue_mutex}; std::unique_lock<std::mutex> lock{queue_mutex};
event_queue.push(*event); event_queue.push(*event);
LOG_DEBUG(Lib_Ime, "ImeState queued event_id={} ({}) queue_size={}", static_cast<u32>(event_id),
event_name_cstr, event_queue.size());
} }
void ImeState::SendEnterEvent() { void ImeState::SendEnterEvent() {
LOG_DEBUG(Lib_Ime, "ImeState::SendEnterEvent triggered");
OrbisImeEvent enterEvent{}; OrbisImeEvent enterEvent{};
enterEvent.id = OrbisImeEventId::PressEnter; enterEvent.id = OrbisImeEventId::PressEnter;
SendEvent(&enterEvent); SendEvent(&enterEvent);
} }
void ImeState::SendCloseEvent() { void ImeState::SendCloseEvent() {
LOG_DEBUG(Lib_Ime, "ImeState::SendCloseEvent triggered (work_buffer={:p})", work_buffer);
OrbisImeEvent closeEvent{}; OrbisImeEvent closeEvent{};
closeEvent.id = OrbisImeEventId::PressClose; closeEvent.id = OrbisImeEventId::PressClose;
closeEvent.param.text.str = reinterpret_cast<char16_t*>(work_buffer); closeEvent.param.text.str = reinterpret_cast<char16_t*>(work_buffer);
SendEvent(&closeEvent); 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<char16_t>::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<std::size_t>(requested_length, buffer_capacity);
if (text_buffer && buffer_capacity > 0) {
const std::size_t copy_length = std::min<std::size_t>(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, bool ImeState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len,
char* utf8_text, std::size_t utf8_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.id = OrbisImeEventId::UpdateText;
event.param.text = eventParam; 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; lastText = currentText;
ui->state->SendEvent(&event); ui->state->SendEvent(&event);
} }
@@ -246,6 +326,12 @@ int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
event.id = OrbisImeEventId::UpdateCaret; event.id = OrbisImeEventId::UpdateCaret;
event.param.caret_move = caretDirection; 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<u32>(caretDirection), caret_direction_cstr);
lastCaretPos = data->CursorPos; lastCaretPos = data->CursorPos;
ui->state->SendEvent(&event); ui->state->SendEvent(&event);
} }

View File

@@ -24,6 +24,7 @@ class ImeState {
void* work_buffer{}; void* work_buffer{};
char16_t* text_buffer{}; char16_t* text_buffer{};
u32 max_text_length = 0;
OrbisImeParamExtended extended_param{}; OrbisImeParamExtended extended_param{};
bool has_extended = false; bool has_extended = false;