Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/libs/prx/libSceIcu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Export.cpp Invariant.cpp Version.c
target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceIcu)
target_sources(libSceIcu PRIVATE CString.cpp)
target_sources(libSceIcu PRIVATE Space.cpp)

if(BUILD_TESTING)
add_test_executable(guest_icu_cstring_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuCString.cpp)
Expand Down Expand Up @@ -29,6 +30,19 @@ if(BUILD_TESTING)
endif()
endif()

if(BUILD_TESTING)
add_test_executable(guest_icu_space_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Space.cpp)
target_include_directories(guest_icu_space_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_icu_space_tests PRIVATE libSceIcu)
configure_windows_unwind(guest_icu_space_tests)
add_test(NAME guest_icu_space COMMAND guest_icu_space_tests)
set_tests_properties(guest_icu_space PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_icu_space PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
endif()
endif()

if(BUILD_TESTING)
add_test_executable(guest_icu_unescape_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Unescape.cpp)
target_include_directories(guest_icu_unescape_tests PRIVATE ${LIBS_INCLUDE_DIR})
Expand Down
25 changes: 0 additions & 25 deletions core/libs/prx/libSceIcu/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,6 @@ int APS5_VABI sceIcuUnknown_58CIUaViW_k_nid_no_patch() {
}
APS5_EXPORT("58CIUaViW+k", sceIcuUnknown_58CIUaViW_k_nid_no_patch);

int APS5_VABI sceIcuUnknown_mY5RbYVLzr4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mY5RbYVLzr4", sceIcuUnknown_mY5RbYVLzr4_nid_no_patch);

int APS5_VABI sceIcuUnknown_1GkIKiuqgBg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
Expand All @@ -341,16 +336,6 @@ int APS5_VABI sceIcuUnknown_wxirhbbuv84_nid_no_patch() {
}
APS5_EXPORT("wxirhbbuv84", sceIcuUnknown_wxirhbbuv84_nid_no_patch);

int APS5_VABI sceIcuUnknown_U089pStD8Bw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("U089pStD8Bw", sceIcuUnknown_U089pStD8Bw_nid_no_patch);

int APS5_VABI sceIcuUnknown_O3zT1gO1ZNM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("O3zT1gO1ZNM", sceIcuUnknown_O3zT1gO1ZNM_nid_no_patch);

int APS5_VABI sceIcuUnknown__L1K53Ku3mE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
Expand All @@ -366,11 +351,6 @@ int APS5_VABI sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch() {
}
APS5_EXPORT("Sv7TbVAEqeQ", sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch);

int APS5_VABI sceIcuUnknown_twrAIl4N_EM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("twrAIl4N+EM", sceIcuUnknown_twrAIl4N_EM_nid_no_patch);

int APS5_VABI sceIcuUnknown_n4o8mgXtF5s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
Expand Down Expand Up @@ -406,11 +386,6 @@ int APS5_VABI sceIcuUnknown_Z5_W18jAIH4_nid_no_patch() {
}
APS5_EXPORT("Z5+W18jAIH4", sceIcuUnknown_Z5_W18jAIH4_nid_no_patch);

int APS5_VABI sceIcuUnknown_tcSjvPP2_4s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tcSjvPP2-4s", sceIcuUnknown_tcSjvPP2_4s_nid_no_patch);

int APS5_VABI sceIcuUnknown_BhCcIlyHMrk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
Expand Down
67 changes: 67 additions & 0 deletions core/libs/prx/libSceIcu/Space.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include "prx/libSceIcu/libSceIcu.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <span>

namespace {

struct Range {
std::int32_t first;
std::int32_t last;
};

constexpr std::array<Range, 7> SpaceSeparators{{
{0x20, 0x20}, {0xa0, 0xa0}, {0x1680, 0x1680}, {0x2000, 0x200a}, {0x202f, 0x202f}, {0x205f, 0x205f}, {0x3000, 0x3000}}};
constexpr std::array<Range, 10> WhiteSpace{{
{0x09, 0x0d}, {0x20, 0x20}, {0x85, 0x85}, {0xa0, 0xa0}, {0x1680, 0x1680},
{0x2000, 0x200a}, {0x2028, 0x2029}, {0x202f, 0x202f}, {0x205f, 0x205f}, {0x3000, 0x3000}}};

bool Contains(std::span<const Range> ranges, std::int32_t character) {
return std::any_of(ranges.begin(), ranges.end(), [character](const Range& range) {
return character >= range.first && character <= range.last;
});
}

bool IsSpaceSeparator(std::int32_t character) {
return Contains(SpaceSeparators, character);
}

bool IsSeparator(std::int32_t character) {
return IsSpaceSeparator(character) || character == 0x2028 || character == 0x2029;
}

bool IsAsciiControlSpace(std::int32_t character) {
return (character >= 0x09 && character <= 0x0d) || (character >= 0x1c && character <= 0x1f);
}

bool IsNoBreakSpace(std::int32_t character) {
return character == 0xa0 || character == 0x2007 || character == 0x202f;
}

}

extern "C" {

std::int8_t APS5_VABI u_isspace_nid_postfix(std::int32_t character) {
return IsSeparator(character) || IsAsciiControlSpace(character) || character == 0x85;
}

std::int8_t APS5_VABI u_isblank_nid_postfix(std::int32_t character) {
if (static_cast<std::uint32_t>(character) <= 0x9f) return character == 0x09 || character == 0x20;
return IsSpaceSeparator(character);
}

std::int8_t APS5_VABI u_isWhitespace_nid_postfix(std::int32_t character) {
return (IsSeparator(character) && !IsNoBreakSpace(character)) || IsAsciiControlSpace(character);
}

std::int8_t APS5_VABI u_isJavaSpaceChar_nid_postfix(std::int32_t character) {
return IsSeparator(character);
}

std::int8_t APS5_VABI u_isUWhiteSpace_nid_postfix(std::int32_t character) {
return Contains(WhiteSpace, character);
}

}
11 changes: 11 additions & 0 deletions core/libs/prx/libSceIcu/Space.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>

extern "C" {
std::int8_t APS5_VABI u_isspace_nid_postfix(std::int32_t);
std::int8_t APS5_VABI u_isblank_nid_postfix(std::int32_t);
std::int8_t APS5_VABI u_isWhitespace_nid_postfix(std::int32_t);
std::int8_t APS5_VABI u_isJavaSpaceChar_nid_postfix(std::int32_t);
std::int8_t APS5_VABI u_isUWhiteSpace_nid_postfix(std::int32_t);
}
1 change: 1 addition & 0 deletions core/libs/prx/libSceIcu/libSceIcu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
#include "Invariant.hpp"
#include "Version.hpp"
#include "CString.hpp"
#include "Space.hpp"
#include "Unescape.hpp"
57 changes: 57 additions & 0 deletions core/libs/prx/libSceIcu/tests/Space.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "prx/libSceIcu/libSceIcu.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <initializer_list>
#include <limits>
#include <vector>

namespace {

using Classifier = std::int8_t (APS5_VABI*)(std::int32_t);

void Check(const char* name, Classifier classify, std::int32_t character, bool expected) {
const int result = classify(character);
if (result == (expected ? 1 : 0)) return;
std::fprintf(stderr, "%s(0x%x) returned %d, expected %d\n", name, static_cast<unsigned>(character), result, expected ? 1 : 0);
std::abort();
}

void Sweep(const char* name, Classifier classify, std::initializer_list<std::int32_t> members) {
std::vector<bool> member(0x110000);
for (const auto character : members) member[character] = true;
for (std::int32_t character = -0x1000; character <= 0x110fff; ++character)
Check(name, classify, character, character >= 0 && character <= 0x10ffff && member[character]);
for (const auto character : members) {
for (const std::uint32_t high : {0x110000u, 0x200000u, 0x1000000u, 0x80000000u, 0xffff0000u})
Check(name, classify, static_cast<std::int32_t>(static_cast<std::uint32_t>(character) + high), false);
}
for (const auto character : {std::numeric_limits<std::int32_t>::min(), -1, 0x110000, std::numeric_limits<std::int32_t>::max()})
Check(name, classify, character, false);
}

}

int main() {
Sweep("u_isspace", u_isspace_nid_postfix, {
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x85, 0xa0, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x202f, 0x205f, 0x3000});
Sweep("u_isblank", u_isblank_nid_postfix, {
0x09, 0x20, 0xa0, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x202f, 0x205f, 0x3000});
Sweep("u_isWhitespace", u_isWhitespace_nid_postfix, {
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x205f, 0x3000});
Sweep("u_isJavaSpaceChar", u_isJavaSpaceChar_nid_postfix, {
0x20, 0xa0, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x202f, 0x205f, 0x3000});
Sweep("u_isUWhiteSpace", u_isUWhiteSpace_nid_postfix, {
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0x85, 0xa0, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x202f, 0x205f, 0x3000});
std::puts("ICU space classification tests passed");
}
4 changes: 3 additions & 1 deletion docs/dev/debt/libSceIcu.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Functional

- [Space classification](../../../core/libs/prx/libSceIcu/Space.cpp) is unverified on a console. `u_isspace`, `u_isblank`, `u_isWhitespace`, `u_isJavaSpaceChar` and `u_isUWhiteSpace` follow ICU 78.1 [uchar.cpp](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/uchar.cpp#L196-L244) over fixed Zs, Zl, Zp and White_Space sets instead of ICU's property data; the sets are unchanged from ICU 65.1 to 78.1. Firmware 13.42 exports `u_isUWhiteSpace` but not the other four ([#11013](https://github.com/boykopovar/AnyPS5/pull/11013#issuecomment-6401310351)), which are kept from the 3.20 inventory for older firmware (see ICU export availability). Closes when: compared with the console.

- [C string helpers](../../../core/libs/prx/libSceIcu/CString.cpp) have no console ABI or behavior verification. Their names/NIDs are in the PS5 3.20 inventory; signatures and behavior follow ICU 78.1's [cstring.cpp](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/cstring.cpp), unchanged since 65.1 apart from casts. Formatting accepts radices 2–16; null buffers or other radices throw an invalid-argument exception, whereas ICU dereferences the buffer and only asserts the radix. Signed minima and outputs exceeding ICU's 30-byte temporary use unsigned arithmetic and caller storage without reproducing upstream undefined behavior. Parsing uses guest LP64 `strtoul` and keeps the low 32 bits; a null source throws. Cross-firmware availability, native invalid-input behavior and the undefined upstream cases remain unverified.

- [Escape decoding](../../../core/libs/prx/libSceIcu/Unescape.cpp) has no behavior or ABI verification on console hardware. It follows ICU 78.1's [ustring.cpp](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/ustring.cpp), including rejection of eight-digit braced hexadecimal escapes although its public header advertises up to eight digits. Null source or offset pointers, null callbacks with an in-range offset, negative capacity with a nonnull destination and source lengths exceeding `INT32_MAX` throw. Null-destination preflight ignores capacity. Closes when: compared with the console, including callback ABI, byte conversion, capacity limits and braced hexadecimal parsing.
Expand All @@ -14,4 +16,4 @@

### Known export names; ABI prototypes unverified

- The 107 `sceIcuUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim.
- The 102 `sceIcuUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceIcu/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim.
Loading