Files
AnyPS5/core/libs/tests/GuestCesSparseProfiles.cpp
T

299 lines
16 KiB
C++

#include "prx/libSceCesCs/libSceCesCs.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
namespace {
constexpr int Unassigned = static_cast<int>(0x805C0020);
constexpr int ShortOutput = static_cast<int>(0x805C0031);
constexpr std::uint32_t Untouched = 0x12345678;
const char* currentProfile = "";
unsigned currentByte = 0;
void Require(bool value, std::source_location location = std::source_location::current()) {
if (value) return;
std::fprintf(stderr, "%s byte 0x%02X line %u\n", currentProfile, currentByte, location.line());
std::abort();
}
struct Profile {
const char* name;
decltype(&sceCesRefersUcsProfileCp1252) get;
std::array<std::uint64_t, 2> undefined;
std::uint64_t checksum;
};
constexpr Profile Profiles[] = {
{"Iso8859_3", sceCesRefersUcsProfileIso8859_3, {0x4000402000000000ULL, 0x0001000800010008ULL}, 0x3A14FC6A7610759CULL},
{"Iso8859_6", sceCesRefersUcsProfileIso8859_6, {0x77FFCFEE00000000ULL, 0xFFF80000F8000001ULL}, 0x9C2E4F6D1F8BAAC9ULL},
{"Iso8859_7", sceCesRefersUcsProfileIso8859_7, {0x0000400000000000ULL, 0x8000000000040000ULL}, 0xA1EC3F5965B3F537ULL},
{"Iso8859_8", sceCesRefersUcsProfileIso8859_8, {0x8000000200000000ULL, 0x980000007FFFFFFFULL}, 0x7ED25AFCF1761A52ULL},
{"Iso8859_11", sceCesRefersUcsProfileIso8859_11, {0x0000000000000000ULL, 0xF000000078000000ULL}, 0x6902E282C2CE9744ULL},
{"Cp857", sceCesRefersUcsProfileCp857, {0x0000000000000000ULL, 0x0004008000200000ULL}, 0x7025402330A56CADULL},
{"Cp869", sceCesRefersUcsProfileCp869, {0x00000000001800BFULL, 0x0000000000000000ULL}, 0x7B0192F88690DE80ULL},
{"Cp874", sceCesRefersUcsProfileCp874, {0x00000000FF01FFDEULL, 0xF000000078000000ULL}, 0xD383EE6DBF9B57B2ULL},
};
void Hash(std::uint64_t& value, std::uint8_t byte) {
value = (value ^ byte) * 1099511628211ULL;
}
void CheckMbcScalar(const std::uint8_t* profile, std::uint8_t byte, const std::uint8_t* expected,
std::uint32_t length, std::uint32_t code, bool undefined) {
const int status = undefined ? Unassigned : 0;
std::array<std::uint8_t, 4> output = {0xA5, 0xA5, 0xA5, 0xA5};
std::uint32_t consumed = Untouched;
std::uint32_t produced = Untouched;
Require(sceCesMbcToUtf8(profile, &byte, 1, &consumed, output.data(), output.size(), &produced) == status);
Require(consumed == 1 && produced == (undefined ? 0 : length));
if (!undefined) Require(std::memcmp(output.data(), expected, length) == 0);
for (std::size_t i = undefined ? 0 : length; i < output.size(); ++i) Require(output[i] == 0xA5);
Require(sceCesMbcToUtf8(profile, &byte, 1, nullptr, output.data(), output.size(), nullptr) == status);
output.fill(0xA5);
consumed = produced = Untouched;
Require(sceCesMbcToUtf8(profile, &byte, 1, &consumed, output.data(), 0, &produced) == (undefined ? Unassigned : ShortOutput));
Require(consumed == (undefined ? 1 : Untouched) && produced == (undefined ? 0 : Untouched));
for (auto value : output) Require(value == 0xA5);
std::uint32_t utf32[2] = {0xA5A5A5A5, 0xA5A5A5A5};
Require(sceCesMbcToUtf32(profile, &byte, 1, &consumed, utf32) == status);
Require(consumed == 1);
Require(utf32[0] == (undefined ? 0 : code) && utf32[1] == 0xA5A5A5A5);
utf32[0] = 0xA5A5A5A5;
Require(sceCesMbcToUtf32(profile, &byte, 1, nullptr, utf32) == status);
Require(utf32[0] == (undefined ? 0 : code) && utf32[1] == 0xA5A5A5A5);
if (undefined) return;
output.fill(0xA5);
produced = Untouched;
Require(sceCesUtf32ToMbc(code, profile, output.data(), 1, &produced) == 0);
Require(produced == 1 && output[0] == byte && output[1] == 0xA5);
output.fill(0xA5);
produced = Untouched;
Require(sceCesUtf32ToMbc(code, profile, output.data(), 0, &produced) == ShortOutput);
Require(produced == 1);
for (auto value : output) Require(value == 0xA5);
consumed = produced = Untouched;
Require(sceCesUtf8ToMbc(expected, length, &consumed, profile, output.data(), 1, &produced) == 0);
Require(consumed == length && produced == 1 && output[0] == byte && output[1] == 0xA5);
output.fill(0xA5);
consumed = produced = Untouched;
Require(sceCesUtf8ToMbc(expected, length, &consumed, profile, output.data(), 0, &produced) == ShortOutput);
Require(consumed == Untouched && produced == Untouched);
for (auto value : output) Require(value == 0xA5);
}
void CheckWideScalar(const std::uint8_t* profile, std::uint8_t byte, std::uint32_t code, bool undefined) {
const int status = undefined ? Unassigned : 0;
std::uint16_t ucs2[2] = {0xA5A5, 0xA5A5};
Require(sceCesSbcToUcs2(profile, byte, ucs2) == status);
Require(ucs2[0] == (undefined ? 0xA5A5 : code) && ucs2[1] == 0xA5A5);
const decltype(&sceCesSbcToUtf16) to16[] = {sceCesSbcToUtf16, sceCesSbcToUtf16be, sceCesSbcToUtf16le};
const decltype(&sceCesSbcToUtf32) to32[] = {sceCesSbcToUtf32, sceCesSbcToUtf32be, sceCesSbcToUtf32le};
for (std::size_t i = 0; i < 3; ++i) {
const std::uint16_t expected16 = i == 1 ? static_cast<std::uint16_t>(code << 8 | code >> 8) : code;
const std::uint32_t expected32 = i == 1 ? (code & 0xff) << 24 | (code & 0xff00) << 8 : code;
ucs2[0] = 0xA5A5;
std::uint32_t length = Untouched;
Require(to16[i](profile, byte, ucs2, 1, &length) == status);
Require(ucs2[0] == (undefined ? 0xA5A5 : expected16) && ucs2[1] == 0xA5A5);
Require(length == (undefined ? Untouched : 1));
Require(to16[i](profile, byte, ucs2, 1, nullptr) == status);
ucs2[0] = 0xA5A5;
length = Untouched;
Require(to16[i](profile, byte, ucs2, 0, &length) == (undefined ? Unassigned : ShortOutput));
Require(ucs2[0] == 0xA5A5 && ucs2[1] == 0xA5A5 && length == Untouched);
std::uint32_t utf32[2] = {0xA5A5A5A5, 0xA5A5A5A5};
Require(to32[i](profile, byte, utf32) == status);
Require(utf32[0] == (undefined ? 0xA5A5A5A5 : expected32) && utf32[1] == 0xA5A5A5A5);
}
}
template<typename TUnit, typename TConvert, typename TLength, typename TReverse, typename TReverseLength>
void CheckWideString(void* context, std::uint8_t byte, TUnit code, bool undefined,
TConvert convert, TLength getLength, TReverse reverse, TReverseLength getReverseLength) {
const TUnit untouched = static_cast<TUnit>(0xA5A5A5A5);
TUnit output[3] = {untouched, untouched, untouched};
const int status = undefined ? Unassigned : 0;
const std::uint32_t units = undefined || byte == 0 ? 0 : 1;
std::uint32_t consumed = Untouched;
std::uint32_t produced = Untouched;
Require(convert(context, &byte, 1, &consumed, output, 2, &produced) == status);
Require(consumed == units && produced == units);
Require(output[0] == (units == 0 ? 0 : code) && output[units] == 0 && output[2] == untouched);
if (units == 0) Require(output[1] == untouched);
consumed = produced = Untouched;
Require(getLength(context, &byte, 1, &consumed, &produced) == status);
Require(consumed == units && produced == units);
if (undefined) {
const std::uint8_t input[] = {'A', byte, 'B'};
Require(convert(context, input, 3, &consumed, output, 3, &produced) == Unassigned);
Require(output[0] == 'A' && output[1] == 0 && output[2] == untouched);
Require(consumed == 1 && produced == 1);
Require(getLength(context, input, 3, &consumed, &produced) == Unassigned);
Require(consumed == 1 && produced == 1);
return;
}
std::uint8_t back[3] = {0xA5, 0xA5, 0xA5};
Require(reverse(context, &code, 1, &consumed, back, 2, &produced) == 0);
Require(consumed == units && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5);
Require(getReverseLength(context, &code, 1, &consumed, &produced) == 0);
Require(consumed == units && produced == units);
code = 0xFFFF;
consumed = produced = Untouched;
back[0] = back[1] = 0xA5;
Require(reverse(context, &code, 1, &consumed, back, 2, &produced) == Unassigned);
Require(consumed == 0 && produced == 0 && back[0] == 0 && back[1] == 0xA5 && back[2] == 0xA5);
Require(getReverseLength(context, &code, 1, &consumed, &produced) == Unassigned);
Require(consumed == 0 && produced == 0);
}
void CheckContext(void* context, std::uint8_t byte, const std::uint8_t* expected, std::uint32_t length, bool undefined) {
std::array<std::uint8_t, 4> output = {0xA5, 0xA5, 0xA5, 0xA5};
std::uint32_t consumed = Untouched;
std::uint32_t produced = Untouched;
const int status = undefined ? Unassigned : 0;
const std::uint32_t units = undefined || byte == 0 ? 0 : 1;
const std::uint32_t bytes = undefined || byte == 0 ? 0 : length;
Require(sceCesMbcsStrToUtf8Str(context, &byte, 1, &consumed, output.data(), output.size(), &produced) == status);
Require(consumed == units && produced == bytes && output[bytes] == 0);
if (!undefined) Require(std::memcmp(output.data(), expected, bytes) == 0);
for (std::size_t i = bytes + 1; i < output.size(); ++i) Require(output[i] == 0xA5);
consumed = produced = Untouched;
Require(sceCesMbcsStrGetUtf8Len(context, &byte, 1, &consumed, &produced) == status);
Require(consumed == units && produced == bytes);
std::uint16_t ucs2[3] = {0xA5A5, 0xA5A5, 0xA5A5};
consumed = produced = Untouched;
Require(sceCesMbcsStrToUcs2Str(context, &byte, 1, &consumed, ucs2, 2, &produced) == status);
Require(consumed == units && produced == units && ucs2[units] == 0 && ucs2[2] == 0xA5A5);
if (units == 0) Require(ucs2[1] == 0xA5A5);
consumed = produced = Untouched;
Require(sceCesMbcsStrGetUcs2Len(context, &byte, 1, &consumed, &produced) == status);
Require(consumed == units && produced == units);
if (undefined) return;
std::uint16_t code = expected[0];
if (length == 2) code = (expected[0] & 0x1F) << 6 | (expected[1] & 0x3F);
if (length == 3) code = (expected[0] & 0x0F) << 12 | (expected[1] & 0x3F) << 6 | (expected[2] & 0x3F);
if (byte != 0) Require(ucs2[0] == code);
std::uint8_t back[3] = {0xA5, 0xA5, 0xA5};
Require(sceCesUtf8StrToMbcsStr(context, expected, length, &consumed, back, 2, &produced) == 0);
Require(consumed == bytes && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5);
Require(sceCesUtf8StrGetMbcsLen(context, expected, length, &consumed, &produced) == 0);
Require(consumed == bytes && produced == units);
back[0] = 0xA5;
Require(sceCesUcs2StrToMbcsStr(context, &code, 1, &consumed, back, 2, &produced) == 0);
Require(consumed == units && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5);
Require(sceCesUcs2StrGetMbcsLen(context, &code, 1, &consumed, &produced) == 0);
Require(consumed == units && produced == units);
if (byte == 0) return;
output.fill(0xA5);
consumed = produced = Untouched;
Require(sceCesMbcsStrToUtf8Str(context, &byte, 1, &consumed, output.data(), length, &produced) == ShortOutput);
Require(consumed == 0 && produced == 0 && output[0] == 0);
for (std::size_t i = 1; i < output.size(); ++i) Require(output[i] == 0xA5);
output.fill(0xA5);
Require(sceCesUtf8StrToMbcsStr(context, expected, length, &consumed, output.data(), 0, &produced) == ShortOutput);
Require(consumed == 0 && produced == 0);
for (auto value : output) Require(value == 0xA5);
}
void CheckUnmapped(const std::uint8_t* profile, void* context) {
const std::uint8_t utf8[] = {0xEF, 0xBF, 0xBF};
const std::uint16_t ucs2 = 0xFFFF;
std::uint8_t output = 0xA5;
std::uint32_t consumed = Untouched;
std::uint32_t produced = Untouched;
Require(sceCesUtf8ToSbc(utf8, sizeof(utf8), &consumed, profile, &output) == Unassigned);
Require(consumed == sizeof(utf8) && output == 0);
Require(sceCesUtf8StrToMbcsStr(context, utf8, sizeof(utf8), &consumed, &output, 1, &produced) == Unassigned);
Require(consumed == 0 && produced == 0 && output == 0);
output = 0xA5;
Require(sceCesUcs2StrToMbcsStr(context, &ucs2, 1, &consumed, &output, 1, &produced) == Unassigned);
Require(consumed == 0 && produced == 0 && output == 0);
output = 0xA5;
produced = Untouched;
Require(sceCesUtf32ToMbc(ucs2, profile, &output, 1, &produced) == Unassigned);
Require(produced == 0 && output == 0xA5);
consumed = produced = Untouched;
Require(sceCesUtf8ToMbc(utf8, sizeof(utf8), &consumed, profile, &output, 1, &produced) == Unassigned);
Require(consumed == Untouched && produced == Untouched && output == 0xA5);
}
void CheckPrefix(void* context, std::uint8_t hole) {
const std::uint8_t input[] = {'A', hole, 'B'};
std::uint8_t output[4] = {0xA5, 0xA5, 0xA5, 0xA5};
std::uint32_t consumed = Untouched;
std::uint32_t produced = Untouched;
Require(sceCesMbcsStrToUtf8Str(context, input, 3, &consumed, output, 4, &produced) == Unassigned);
Require(consumed == 1 && produced == 1);
Require(output[0] == 'A' && output[1] == 0 && output[2] == 0xA5 && output[3] == 0xA5);
Require(sceCesMbcsStrGetUtf8Len(context, input, 3, &consumed, &produced) == Unassigned);
Require(consumed == 1 && produced == 1);
const std::uint8_t terminated[] = {'A', 0, hole};
Require(sceCesMbcsStrToUtf8Str(context, terminated, 3, &consumed, output, 4, &produced) == 0);
Require(consumed == 1 && produced == 1 && output[1] == 0);
}
void CheckProfile(const Profile& fixture) {
currentProfile = fixture.name;
const auto* profile = fixture.get();
Require(profile && fixture.get() == profile && profile != sceCesRefersUcsProfileCp1252());
alignas(8) std::uint8_t context[256] = {};
Require(sceCesMbcsUcsContextInit(context, profile) == 0);
std::uint64_t checksum = 14695981039346656037ULL;
for (currentByte = 0; currentByte < 256; ++currentByte) {
const auto byte = static_cast<std::uint8_t>(currentByte);
const bool undefined = byte >= 128 && (fixture.undefined[(byte - 128) / 64] >> ((byte - 128) % 64) & 1) != 0;
std::array<std::uint8_t, 4> utf8 = {0xA5, 0xA5, 0xA5, 0xA5};
std::uint32_t length = Untouched;
Require(sceCesSbcToUtf8(profile, byte, utf8.data(), utf8.size(), &length) == (undefined ? Unassigned : 0));
std::uint32_t code = utf8[0];
if (length == 2) code = (utf8[0] & 0x1F) << 6 | (utf8[1] & 0x3F);
if (length == 3) code = (utf8[0] & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F);
CheckWideScalar(profile, byte, code, undefined);
CheckMbcScalar(profile, byte, utf8.data(), length, code, undefined);
CheckWideString(context, byte, static_cast<std::uint16_t>(code), undefined,
sceCesMbcsStrToUtf16Str, sceCesMbcsStrGetUtf16Len, sceCesUtf16StrToMbcsStr, sceCesUtf16StrGetMbcsLen);
CheckWideString(context, byte, code, undefined,
sceCesMbcsStrToUtf32Str, sceCesMbcsStrGetUtf32Len, sceCesUtf32StrToMbcsStr, sceCesUtf32StrGetMbcsLen);
if (undefined) {
Require(length == 0);
for (auto value : utf8) Require(value == 0xA5);
CheckContext(context, byte, utf8.data(), 0, true);
CheckPrefix(context, byte);
continue;
}
Require(length > 0 && length <= 3);
Hash(checksum, static_cast<std::uint8_t>(length));
for (std::uint32_t i = 0; i < length; ++i) Hash(checksum, utf8[i]);
for (std::uint32_t i = length; i < utf8.size(); ++i) Require(utf8[i] == 0xA5);
std::uint8_t back = 0xA5;
std::uint32_t consumed = Untouched;
Require(sceCesUtf8ToSbc(utf8.data(), length, &consumed, profile, &back) == 0 && consumed == length && back == byte);
Require(sceCesUtf8ToSbc(utf8.data(), length, nullptr, profile, &back) == 0 && back == byte);
CheckContext(context, byte, utf8.data(), length, false);
std::array<std::uint8_t, 4> shortOutput = {0xA5, 0xA5, 0xA5, 0xA5};
consumed = Untouched;
Require(sceCesSbcToUtf8(profile, byte, shortOutput.data(), length - 1, &consumed) == ShortOutput);
Require(consumed == length);
for (auto value : shortOutput) Require(value == 0xA5);
Require(sceCesSbcToUtf8(profile, byte, shortOutput.data(), length, nullptr) == 0);
Require(std::memcmp(shortOutput.data(), utf8.data(), length) == 0);
}
Require(checksum == fixture.checksum);
CheckUnmapped(profile, context);
}
} // namespace
int main() {
for (const auto& profile : Profiles) CheckProfile(profile);
for (std::size_t i = 0; i < std::size(Profiles); ++i) {
for (std::size_t j = 0; j < i; ++j) Require(Profiles[i].get() != Profiles[j].get());
}
}