Files
AnyPS5/core/libs/prx/libSceIme/Export.cpp
T

436 lines
17 KiB
C++

#include <algorithm>
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <deque>
#include <mutex>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceIme/include/ImeInput.hpp"
namespace {
constexpr int ErrorBusy = static_cast<int>(0x80bc0001u);
constexpr int ErrorNotOpened = static_cast<int>(0x80bc0002u);
constexpr int ErrorConnectionFailed = static_cast<int>(0x80bc0004u);
constexpr int ErrorInvalidText = static_cast<int>(0x80bc0006u);
constexpr int ErrorInvalidUserId = static_cast<int>(0x80bc0010u);
constexpr int ErrorInvalidType = static_cast<int>(0x80bc0011u);
constexpr int ErrorInvalidEnterLabel = static_cast<int>(0x80bc0013u);
constexpr int ErrorInvalidInputMethod = static_cast<int>(0x80bc0014u);
constexpr int ErrorInvalidOption = static_cast<int>(0x80bc0015u);
constexpr int ErrorInvalidMaxTextLength = static_cast<int>(0x80bc0016u);
constexpr int ErrorInvalidInputTextBuffer = static_cast<int>(0x80bc0017u);
constexpr int ErrorInvalidPosX = static_cast<int>(0x80bc0018u);
constexpr int ErrorInvalidPosY = static_cast<int>(0x80bc0019u);
constexpr int ErrorInvalidHorizontalAlignment = static_cast<int>(0x80bc001au);
constexpr int ErrorInvalidVerticalAlignment = static_cast<int>(0x80bc001bu);
constexpr int ErrorInvalidExtended = static_cast<int>(0x80bc001cu);
constexpr int ErrorInvalidWork = static_cast<int>(0x80bc0020u);
constexpr int ErrorInvalidHandler = static_cast<int>(0x80bc0022u);
constexpr int ErrorNoResourceId = static_cast<int>(0x80bc0023u);
constexpr int ErrorInvalidMode = static_cast<int>(0x80bc0024u);
constexpr int ErrorInvalidParam = static_cast<int>(0x80bc0030u);
constexpr int ErrorInvalidAddress = static_cast<int>(0x80bc0031u);
constexpr int ErrorInvalidReserved = static_cast<int>(0x80bc0032u);
constexpr uint32_t ValidKeyboardModes = 0x0000007f;
constexpr uint32_t TypeNumber = 4;
constexpr uint32_t ValidOptions = 0x00007bff;
constexpr uint32_t OptionMultiline = 0x00000001;
constexpr uint32_t OptionUseOver2K = 0x00004000;
constexpr uint32_t EnterLabelLast = 3;
constexpr uint32_t AlignmentLast = 2;
constexpr uint32_t PanelPriorityLast = 3;
constexpr uint32_t MaxTextLength = 2048;
constexpr int32_t UserIdInvalid = -1;
constexpr uint32_t EventOpen = 0;
constexpr uint32_t EventUpdateText = 1;
constexpr uint32_t EventUpdateCaret = 2;
constexpr uint32_t EventPressClose = 4;
constexpr uint32_t EventPressEnter = 5;
constexpr uint32_t CaretMoveLeft = 1;
constexpr uint32_t CaretMoveRight = 2;
struct Session {
Param param{};
std::u16string text;
std::uint32_t caret = 0;
std::deque<ImeEvent> events;
};
std::mutex g_keyboardMutex;
std::set<int32_t> g_openKeyboards;
std::mutex g_imeMutex;
std::optional<Session> g_session;
void PanelSize(const Param& param, uint32_t* width, uint32_t* height) {
const uint32_t scale = (param.option & OptionUseOver2K) != 0 ? 2 : 1;
*width = (param.type == TypeNumber ? 370u : 793u) * scale;
*height = (param.type == TypeNumber ? 402u : 408u) * scale;
}
bool IsHighSurrogate(char16_t unit) { return unit >= 0xd800 && unit <= 0xdbff; }
bool IsLowSurrogate(char16_t unit) { return unit >= 0xdc00 && unit <= 0xdfff; }
bool ValidText(const char16_t* text, std::size_t length, bool multiline) {
for (std::size_t i = 0; i < length; ++i) {
const char16_t unit = text[i];
if (!multiline && (unit == u'\n' || unit == u'\r')) return false;
if (IsLowSurrogate(unit)) return false;
if (IsHighSurrogate(unit)) {
if (i + 1 >= length || !IsLowSurrogate(text[i + 1])) return false;
++i;
}
}
return true;
}
std::size_t TextLength(const char16_t* text, std::size_t limit) {
std::size_t length = 0;
while (length < limit && text[length] != u'\0') ++length;
return length;
}
std::u16string DecodeUtf8(const char* text, std::size_t size) {
std::u16string units;
for (std::size_t i = 0; i < size;) {
const auto lead = static_cast<unsigned char>(text[i]);
std::uint32_t codePoint = 0;
std::size_t extra = 0;
if (lead < 0x80) codePoint = lead;
else if ((lead & 0xe0) == 0xc0) { codePoint = lead & 0x1f; extra = 1; }
else if ((lead & 0xf0) == 0xe0) { codePoint = lead & 0x0f; extra = 2; }
else if ((lead & 0xf8) == 0xf0) { codePoint = lead & 0x07; extra = 3; }
else throw std::invalid_argument("IME host input: invalid UTF-8 lead byte");
if (extra != 0 && i + extra >= size) throw std::invalid_argument("IME host input: truncated UTF-8 sequence");
for (std::size_t k = 1; k <= extra; ++k) {
const auto next = static_cast<unsigned char>(text[i + k]);
if ((next & 0xc0) != 0x80) throw std::invalid_argument("IME host input: invalid UTF-8 continuation byte");
codePoint = (codePoint << 6) | (next & 0x3f);
}
i += extra + 1;
if (codePoint >= 0x10000) {
codePoint -= 0x10000;
units.push_back(static_cast<char16_t>(0xd800 + (codePoint >> 10)));
units.push_back(static_cast<char16_t>(0xdc00 + (codePoint & 0x3ff)));
} else {
units.push_back(static_cast<char16_t>(codePoint));
}
}
return units;
}
void SyncBuffers(Session& session) {
const std::size_t bytes = session.text.size() * sizeof(char16_t);
std::memcpy(session.param.input_text_buffer, session.text.data(), bytes);
session.param.input_text_buffer[session.text.size()] = u'\0';
auto* work = static_cast<char16_t*>(session.param.work);
std::memcpy(work, session.text.data(), bytes);
work[session.text.size()] = u'\0';
}
void QueueText(Session& session, uint32_t id, uint32_t index, int32_t delta) {
ImeEvent event{};
event.id = id;
event.param.text.str = static_cast<char16_t*>(session.param.work);
event.param.text.caret_index = session.caret;
event.param.text.area_num = 1;
event.param.text.text_area[0].mode = TextAreaMode::Edit;
event.param.text.text_area[0].index = index;
event.param.text.text_area[0].length = delta;
session.events.push_back(event);
}
void QueueCaretSteps(Session& session, uint32_t direction, std::size_t steps) {
for (std::size_t i = 0; i < steps; ++i) {
ImeEvent event{};
event.id = EventUpdateCaret;
event.param.caret_move = direction;
session.events.push_back(event);
}
}
void Insert(Session& session, std::u16string units) {
const bool multiline = (session.param.option & OptionMultiline) != 0;
std::u16string accepted;
for (const char16_t unit : units) {
if (!multiline && (unit == u'\n' || unit == u'\r')) continue;
accepted.push_back(unit);
}
const std::size_t room = session.param.max_text_length - session.text.size();
std::size_t count = std::min(accepted.size(), room);
if (count > 0 && count < accepted.size() && IsHighSurrogate(accepted[count - 1])) --count;
if (count == 0) return;
const uint32_t index = session.caret;
session.text.insert(index, accepted, 0, count);
session.caret += static_cast<uint32_t>(count);
SyncBuffers(session);
QueueText(session, EventUpdateText, index, static_cast<int32_t>(count));
}
std::size_t UnitsBefore(const Session& session) {
const uint32_t caret = session.caret;
if (caret == 0) return 0;
return caret >= 2 && IsLowSurrogate(session.text[caret - 1]) && IsHighSurrogate(session.text[caret - 2]) ? 2 : 1;
}
std::size_t UnitsAfter(const Session& session) {
const uint32_t caret = session.caret;
if (caret >= session.text.size()) return 0;
return caret + 1 < session.text.size() && IsHighSurrogate(session.text[caret]) && IsLowSurrogate(session.text[caret + 1]) ? 2 : 1;
}
void Apply(Session& session, const ImeHostInput& input) {
switch (input.kind) {
case ImeHostInputKind::Text: {
std::size_t size = 0;
while (size < sizeof(input.text) && input.text[size] != '\0') ++size;
Insert(session, DecodeUtf8(input.text, size));
return;
}
case ImeHostInputKind::Backspace: {
const std::size_t units = UnitsBefore(session);
if (units == 0) return;
session.caret -= static_cast<uint32_t>(units);
session.text.erase(session.caret, units);
SyncBuffers(session);
QueueText(session, EventUpdateText, session.caret, -static_cast<int32_t>(units));
return;
}
case ImeHostInputKind::Delete: {
const std::size_t units = UnitsAfter(session);
if (units == 0) return;
session.text.erase(session.caret, units);
SyncBuffers(session);
QueueText(session, EventUpdateText, session.caret, -static_cast<int32_t>(units));
return;
}
case ImeHostInputKind::Left: {
const std::size_t units = UnitsBefore(session);
session.caret -= static_cast<uint32_t>(units);
QueueCaretSteps(session, CaretMoveLeft, units);
return;
}
case ImeHostInputKind::Right: {
const std::size_t units = UnitsAfter(session);
session.caret += static_cast<uint32_t>(units);
QueueCaretSteps(session, CaretMoveRight, units);
return;
}
case ImeHostInputKind::Home: {
const std::size_t units = session.caret;
session.caret = 0;
QueueCaretSteps(session, CaretMoveLeft, units);
return;
}
case ImeHostInputKind::End: {
const std::size_t units = session.text.size() - session.caret;
session.caret = static_cast<uint32_t>(session.text.size());
QueueCaretSteps(session, CaretMoveRight, units);
return;
}
case ImeHostInputKind::Enter:
if ((session.param.option & OptionMultiline) != 0) {
Insert(session, u"\n");
} else {
QueueText(session, EventPressEnter, session.caret, 0);
}
return;
case ImeHostInputKind::Escape:
QueueText(session, EventPressClose, session.caret, 0);
return;
}
throw std::invalid_argument("IME host input: unknown input kind");
}
int ValidateOpen(const Param* param, const ExtendedParam* extended) {
if (!param) return ErrorInvalidAddress;
if (param->user_id == UserIdInvalid) return ErrorInvalidUserId;
if (param->type > TypeNumber) return ErrorInvalidType;
if (param->enter_label > EnterLabelLast) return ErrorInvalidEnterLabel;
if (param->input_method != 0) return ErrorInvalidInputMethod;
if ((param->option & ~ValidOptions) != 0) return ErrorInvalidOption;
if (param->max_text_length == 0 || param->max_text_length > MaxTextLength) return ErrorInvalidMaxTextLength;
if (!param->input_text_buffer) return ErrorInvalidInputTextBuffer;
const float maxX = (param->option & OptionUseOver2K) != 0 ? 3840.0f : 1920.0f;
const float maxY = (param->option & OptionUseOver2K) != 0 ? 2160.0f : 1080.0f;
if (!(param->posx >= 0.0f && param->posx < maxX)) return ErrorInvalidPosX;
if (!(param->posy >= 0.0f && param->posy < maxY)) return ErrorInvalidPosY;
if (param->horizontal_alignment > AlignmentLast) return ErrorInvalidHorizontalAlignment;
if (param->vertical_alignment > AlignmentLast) return ErrorInvalidVerticalAlignment;
if (extended) {
if (extended->priority > PanelPriorityLast) return ErrorInvalidExtended;
for (const std::int8_t byte : extended->reserved) {
if (byte != 0) return ErrorInvalidExtended;
}
}
if (!param->work || (reinterpret_cast<std::uintptr_t>(param->work) & 3u) != 0) return ErrorInvalidWork;
if (!param->handler) return ErrorInvalidHandler;
for (const std::int8_t byte : param->reserved) {
if (byte != 0) return ErrorInvalidReserved;
}
const std::size_t length = TextLength(param->input_text_buffer, param->max_text_length);
if (!ValidText(param->input_text_buffer, length, (param->option & OptionMultiline) != 0)) return ErrorInvalidText;
return 0;
}
}
extern "C" {
void ImePublishInput_nid_postfix(const ImeHostInput& input) {
std::lock_guard lock(g_imeMutex);
if (g_session) Apply(*g_session, input);
}
bool ImeCapturesKeyboard_nid_postfix() {
std::lock_guard lock(g_imeMutex);
return g_session.has_value();
}
int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended) {
std::lock_guard lock(g_imeMutex);
if (g_session) return ErrorBusy;
if (const int error = ValidateOpen(param, extended)) return error;
if (param->filter) NotImplemented_nid_no_patch("sceImeOpen: text filter");
if (extended && extended->ext_keyboard_filter) NotImplemented_nid_no_patch("sceImeOpen: external keyboard filter");
Session session;
session.param = *param;
session.text.assign(param->input_text_buffer, TextLength(param->input_text_buffer, param->max_text_length));
session.caret = static_cast<uint32_t>(session.text.size());
SyncBuffers(session);
ImeEvent open{};
open.id = EventOpen;
open.param.rect.x = param->posx;
open.param.rect.y = param->posy;
PanelSize(*param, &open.param.rect.width, &open.param.rect.height);
session.events.push_back(open);
g_session = std::move(session);
return 0;
}
int APS5_VABI sceImeClose_nid_postfix(void) {
std::lock_guard lock(g_imeMutex);
if (!g_session) return ErrorNotOpened;
g_session.reset();
return 0;
}
int APS5_VABI sceImeGetPanelPositionAndForm(PositionAndForm* form) {
std::lock_guard lock(g_imeMutex);
if (!g_session) return ErrorNotOpened;
if (!form) return ErrorInvalidAddress;
const Param& param = g_session->param;
*form = {};
form->type = param.type;
form->posx = param.posx;
form->posy = param.posy;
form->horizontal_alignment = param.horizontal_alignment;
form->vertical_alignment = param.vertical_alignment;
PanelSize(param, &form->width, &form->height);
return 0;
}
int APS5_VABI sceImeGetPanelSize(const Param* param, uint32_t* width, uint32_t* height) {
if (!param || !width || !height) return ErrorInvalidAddress;
if (param->type > TypeNumber) return ErrorInvalidType;
if ((param->option & ~ValidOptions) != 0) return ErrorInvalidOption;
PanelSize(*param, width, height);
return 0;
}
int APS5_VABI sceImeKeyboardClose(int32_t user_id) {
std::lock_guard lock(g_keyboardMutex);
if (g_openKeyboards.erase(user_id) == 0) return ErrorNotOpened;
return 0;
}
int APS5_VABI sceImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info) {
(void)resource_id;
if (!info) return ErrorInvalidAddress;
std::lock_guard lock(g_keyboardMutex);
return g_openKeyboards.empty() ? ErrorNotOpened : ErrorNoResourceId;
}
int APS5_VABI sceImeKeyboardGetResourceId(int32_t user_id, KeyboardResourceIdArray* resource_ids) {
if (!resource_ids) return ErrorInvalidAddress;
if (user_id == UserIdInvalid) return ErrorInvalidUserId;
*resource_ids = {};
resource_ids->user_id = user_id;
std::lock_guard lock(g_keyboardMutex);
return g_openKeyboards.contains(user_id) ? ErrorConnectionFailed : ErrorNotOpened;
}
int APS5_VABI sceImeKeyboardOpen(int32_t user_id, const KeyboardParam* param) {
if (!param) APS5_INVALID_ARG_EX;
std::lock_guard lock(g_keyboardMutex);
if (!g_openKeyboards.insert(user_id).second) return ErrorBusy;
return 0;
}
int APS5_VABI sceImeKeyboardSetMode(int32_t user_id, uint32_t mode) {
if (user_id == UserIdInvalid) return ErrorInvalidUserId;
std::lock_guard lock(g_keyboardMutex);
if (!g_openKeyboards.contains(user_id)) return ErrorNotOpened;
return (mode & ~ValidKeyboardModes) == 0 ? 0 : ErrorInvalidMode;
}
void APS5_VABI sceImeParamInit(Param* param) {
if (!param) return;
std::memset(param, 0, sizeof(*param));
param->user_id = -1;
}
int APS5_VABI sceImeSetCaret(const Caret* caret) {
std::lock_guard lock(g_imeMutex);
if (!g_session) return ErrorNotOpened;
if (!caret) return ErrorInvalidAddress;
if (caret->index > g_session->text.size()) return ErrorInvalidParam;
g_session->caret = caret->index;
return 0;
}
int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length) {
std::lock_guard lock(g_imeMutex);
if (!g_session) return ErrorNotOpened;
if (!text) return ErrorInvalidAddress;
Session& session = *g_session;
const std::size_t count = TextLength(text, std::min(length, session.param.max_text_length));
if (!ValidText(text, count, (session.param.option & OptionMultiline) != 0)) return ErrorInvalidText;
session.text.assign(text, count);
if (session.caret > session.text.size()) session.caret = static_cast<uint32_t>(session.text.size());
SyncBuffers(session);
return 0;
}
int APS5_VABI sceImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geometry) {
(void)mode;
std::lock_guard lock(g_imeMutex);
if (!g_session) return ErrorNotOpened;
if (!geometry) return ErrorInvalidAddress;
return 0;
}
int APS5_VABI sceImeUpdate(EventHandler handler) {
if (!handler) APS5_INVALID_ARG_EX;
for (;;) {
ImeEvent event{};
void* arg = nullptr;
{
std::lock_guard lock(g_imeMutex);
if (!g_session || g_session->param.handler != handler || g_session->events.empty()) return 0;
event = g_session->events.front();
g_session->events.pop_front();
arg = g_session->param.arg;
}
handler(arg, &event);
}
}
}