Files
AnyPS5/core/libs/prx/libSceIcu/JavaUtf8.cpp
T
MarcZX10 691c469caa feat(libSceIcu): implement u_strFromJavaModifiedUTF8WithSub
Convert Java modified UTF-8 to UTF-16 following ICU 78.1 ustrtrns.cpp.
One to three byte sequences are decoded without the overlong and
surrogate checks of standard UTF-8, so C0 80 is U+0000 and encoded
surrogates pass through; four byte sequences and truncated or malformed
ones are replaced by the substitution character, or fail with
U_INVALID_CHAR_FOUND when it is U_SENTINEL. The bytes a malformed sequence
swallows follow ICU's utf8_nextCharSafeBody. As in ICU, once the
destination is full each substituted sequence adds one unit to the
preflight length even when the substitution character is supplementary.

The sceIcuUnknown stub with this NID is removed.
2026-11-05 21:54:05 +00:00

104 lines
4.1 KiB
C++

#include "prx/libSceIcu/libSceIcu.hpp"
#include "prx/libSceIcu/Status.hpp"
#include <cstdint>
#include <cstring>
namespace {
bool Continuation(std::uint8_t byte) {
return byte >= 0x80 && byte <= 0xbf;
}
bool ThreeByteSecond(std::uint8_t lead, std::uint8_t byte) {
if (lead == 0xe0) return byte >= 0xa0 && byte <= 0xbf;
if (lead == 0xed) return byte >= 0x80 && byte <= 0x9f;
return Continuation(byte);
}
bool FourByteSecond(std::uint8_t lead, std::uint8_t byte) {
if (lead == 0xf0) return byte >= 0x90 && byte <= 0xbf;
if (lead == 0xf4) return byte >= 0x80 && byte <= 0x8f;
return lead >= 0xf1 && lead <= 0xf3 && Continuation(byte);
}
std::int64_t Skipped(const std::uint8_t* source, std::int64_t lead, std::int64_t length) {
const auto first = source[lead];
const auto at = [&](std::int64_t offset) { return lead + offset < length ? source[lead + offset] : 0; };
if (first >= 0xf0 && first <= 0xf4) {
if (lead + 1 >= length || !FourByteSecond(first, source[lead + 1])) return 1;
if (!Continuation(at(2))) return 2;
if (!Continuation(at(3))) return 3;
return 4;
}
if (first >= 0xe0 && first <= 0xef) {
if (lead + 1 >= length || !ThreeByteSecond(first, source[lead + 1])) return 1;
return 2;
}
return 1;
}
}
extern "C" {
char16_t* APS5_VABI u_strFromJavaModifiedUTF8WithSub_nid_postfix(char16_t* destination, std::int32_t capacity, std::int32_t* destinationLength,
const char* text, std::int32_t sourceLength, std::int32_t substitution,
std::int32_t* substitutions, std::int32_t* status) {
if (Icu::Failure(status)) return nullptr;
if ((!text && sourceLength != 0) || sourceLength < -1 || (!destination && capacity != 0) || capacity < 0 ||
substitution > 0x10ffff || Icu::Surrogate(substitution)) {
*status = Icu::IllegalArgument;
return nullptr;
}
if (substitutions) *substitutions = 0;
const auto* source = reinterpret_cast<const std::uint8_t*>(text);
const std::int64_t length = sourceLength < 0 ? static_cast<std::int64_t>(std::strlen(text)) : sourceLength;
std::int64_t written = 0;
std::int64_t extra = 0;
std::int32_t replaced = 0;
for (std::int64_t index = 0; index < length;) {
const auto lead = source[index];
const bool writing = written < capacity;
char16_t unit = lead;
std::int64_t used = 1;
bool valid = true;
if (lead >= 0xc0 && lead <= 0xdf && index + 1 < length && Continuation(source[index + 1])) {
unit = static_cast<char16_t>(((lead & 0x1f) << 6) | (source[index + 1] & 0x3f));
used = 2;
} else if (lead >= 0xe0 && lead <= 0xef && index + 2 < length && Continuation(source[index + 1]) && Continuation(source[index + 2])) {
unit = static_cast<char16_t>(((lead & 0x0f) << 12) | ((source[index + 1] & 0x3f) << 6) | (source[index + 2] & 0x3f));
used = 3;
} else if (lead >= 0x80) {
valid = false;
}
if (valid) {
index += used;
if (writing) destination[written++] = unit;
else ++extra;
continue;
}
if (substitution < 0) {
*status = Icu::InvalidCharacter;
return nullptr;
}
index += Skipped(source, index, length);
++replaced;
if (!writing) {
++extra;
} else if (substitution <= 0xffff) {
destination[written++] = static_cast<char16_t>(substitution);
} else {
destination[written++] = static_cast<char16_t>(0xd7c0 + (substitution >> 10));
if (written < capacity) destination[written++] = static_cast<char16_t>(0xdc00 + (substitution & 0x3ff));
else ++extra;
}
}
const auto required = Icu::Checked(written + extra);
if (substitutions) *substitutions = replaced;
if (destinationLength) *destinationLength = required;
Icu::Terminate(destination, capacity, required, status);
return destination;
}
}