diff --git a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt index 9d52b13aa7..4d34176d1f 100644 --- a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt +++ b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(libSceFreeTypeOt SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp) target_include_directories(libSceFreeTypeOt PRIVATE ${LIBS_INCLUDE_DIR}) +include("${CMAKE_CURRENT_LIST_DIR}/Stream.cmake") configure_windows_unwind(libSceFreeTypeOt) if(BUILD_TESTING) diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index 737f485d20..8669bd9fe2 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -385,86 +385,6 @@ int APS5_VABI sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch() { } APS5_EXPORT("JgtSc0g3G6E", sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("gVf7xRLdWm8", sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("FHdeCFVf-f8", sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Qmgc0XACzRg", sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("0rGx-oFMLyk", sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("thA48N-0VwQ", sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("jl0VxYZb8-o", sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Er0Lin-u20o", sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("KHj92a3WDn4", sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("tDssQCH2syY", sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("dXJK59vXRxs", sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BB4gl7SgLdk", sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Fv8MWCUH8k8", sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("RzgDJTjQst4", sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("4yt9cD1qcEI", sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("u6Xquj1bFwc", sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("-Xm1cLEpBec", sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch); - int APS5_VABI sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -475,106 +395,6 @@ int APS5_VABI sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch() { } APS5_EXPORT("iruPsrXCN7U", sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("a7RlARfexoo", sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("jX1zq1i8KfI", sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("U+0WKVAePww", sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("dlbnv6Wass8", sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("cmd41395g7Y", sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("SX5I+XzH8HI", sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Bmi8PdvsM3M", sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("jJh4SMt8NFQ", sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("1z6bBg1qBfU", sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("mF0B64O954g", sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("c002A4zTOgA", sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("mcT796Qgj+g", sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("b5gZp-r3WWI", sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("S+n0Md8Rjfw", sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("6a1p3QgXvo0", sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("JFSCOXTEvfg", sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("xejRyEbsLcw", sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("R1mZutnlTiM", sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("4J4Baf8ana8", sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Az4H9FlfxCA", sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch); - int APS5_VABI sceFreeTypeOtStrokerBeginSubPath_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceFreeTypeOt/Stream.cmake b/core/libs/prx/libSceFreeTypeOt/Stream.cmake new file mode 100644 index 0000000000..6d0612678d --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/Stream.cmake @@ -0,0 +1,23 @@ +target_sources(libSceFreeTypeOt PRIVATE ${CMAKE_CURRENT_LIST_DIR}/Stream.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_stream_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeStream.cpp) + target_include_directories(guest_freetype_stream_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_stream_tests PRIVATE ${CMAKE_DL_LIBS}) + if(WIN32) + target_link_libraries(guest_freetype_stream_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_stream_tests) + add_custom_target(guest_freetype_stream_libraries DEPENDS + libSceFreeTypeOt ${PATCHED_DIR}/libSceFreeTypeOt.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED}) + add_dependencies(guest_freetype_stream_tests guest_freetype_stream_libraries) + add_test(NAME guest_freetype_stream COMMAND guest_freetype_stream_tests $ raw) + add_test(NAME guest_freetype_stream_patched COMMAND guest_freetype_stream_tests ${PATCHED_DIR}/libSceFreeTypeOt.prx patched) + set_tests_properties(guest_freetype_stream guest_freetype_stream_patched PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_stream PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_stream_patched PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() +endif() diff --git a/core/libs/prx/libSceFreeTypeOt/Stream.cpp b/core/libs/prx/libSceFreeTypeOt/Stream.cpp new file mode 100644 index 0000000000..ae0603aa01 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/Stream.cpp @@ -0,0 +1,461 @@ +#include "prx/libSceFreeTypeOt/include/Stream.hpp" + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +constexpr int InvalidArgument = 0x06; +constexpr int OutOfMemory = 0x40; +constexpr int InvalidStreamOperation = 0x55; + +constexpr std::uint8_t FrameStart = 4; +constexpr std::uint8_t FrameBytes = 24; +constexpr std::uint8_t FrameSkip = 25; +constexpr std::uint8_t FrameSigned = 1; + +[[noreturn]] void Unsupported(const char* function, const char* what) { + throw std::invalid_argument(std::string(function) + ": " + what); +} + +GuestStreamRec& Stream(GuestStreamRec* stream, const char* function) { + if (!stream) Unsupported(function, "null stream"); + return *stream; +} + +std::uintptr_t Address(const void* pointer) { + return reinterpret_cast(pointer); +} + +std::uint8_t* Offset(const void* pointer, std::uint64_t count) { + return reinterpret_cast(Address(pointer) + count); +} + +std::uint64_t BigEndian(const std::uint8_t* bytes, unsigned count) { + std::uint64_t value = 0; + for (unsigned index = 0; index < count; ++index) value = value << 8 | bytes[index]; + return value; +} + +std::uint64_t LittleEndian(const std::uint8_t* bytes, unsigned count) { + std::uint64_t value = 0; + for (unsigned index = count; index > 0; --index) value = value << 8 | bytes[index - 1]; + return value; +} + +std::int64_t SignExtend(std::uint64_t value, unsigned bytes) { + const unsigned shift = 64 - bytes * 8; + return static_cast(value << shift) >> shift; +} + +void CopyFromBase(std::uint8_t* buffer, const GuestStreamRec& stream, std::uint64_t pos, std::uint64_t count, const char* function) { + if (count == 0) return; + if (!buffer) Unsupported(function, "null buffer"); + if (!stream.base) Unsupported(function, "null memory stream base"); + std::memmove(buffer, stream.base + pos, count); +} + +std::uint8_t* QAlloc(GuestMemoryRec* memory, std::int64_t size, int& error, const char* function) { + error = 0; + if (size > 0) { + if (!memory || !memory->alloc || !memory->free) Unsupported(function, "null memory allocator or release"); + auto* block = static_cast(memory->alloc(memory, size)); + if (!block) error = OutOfMemory; + return block; + } + if (size < 0) error = InvalidArgument; + return nullptr; +} + +void Free(GuestMemoryRec* memory, void* block, const char* function) { + if (!block) return; + if (!memory || !memory->free) Unsupported(function, "null memory release"); + memory->free(memory, block); +} + +int Seek(GuestStreamRec* stream, std::uint64_t pos, const char* function) { + auto& record = Stream(stream, function); + int error = 0; + if (record.read) { + if (record.read(stream, pos, nullptr, 0)) error = InvalidStreamOperation; + } else if (pos > record.size) { + error = InvalidStreamOperation; + } + if (!error) record.pos = pos; + return error; +} + +int ReadAt(GuestStreamRec* stream, std::uint64_t pos, std::uint8_t* buffer, std::uint64_t count, const char* function) { + auto& record = Stream(stream, function); + if (pos >= record.size) return InvalidStreamOperation; + std::uint64_t readBytes; + if (record.read) { + readBytes = record.read(stream, pos, buffer, count); + } else { + readBytes = record.size - pos; + if (readBytes > count) readBytes = count; + CopyFromBase(buffer, record, pos, readBytes, function); + } + record.pos = pos + readBytes; + return readBytes < count ? InvalidStreamOperation : 0; +} + +int EnterFrame(GuestStreamRec* stream, std::uint64_t count, const char* function) { + auto& record = Stream(stream, function); + int error = 0; + if (record.read) { + auto* memory = record.memory; + if (count > record.size) return InvalidStreamOperation; + record.base = QAlloc(memory, static_cast(count), error, function); + if (error) return error; + const std::uint64_t readBytes = record.read(stream, record.pos, record.base, count); + if (readBytes < count) { + Free(memory, record.base, function); + record.base = nullptr; + error = InvalidStreamOperation; + } + record.cursor = record.base; + record.limit = Offset(record.cursor, count); + record.pos += readBytes; + } else { + if (record.pos >= record.size || record.size - record.pos < count) return InvalidStreamOperation; + record.cursor = Offset(record.base, record.pos); + record.limit = Offset(record.cursor, count); + record.pos += count; + } + return error; +} + +void ExitFrame(GuestStreamRec* stream, const char* function) { + auto& record = Stream(stream, function); + if (record.read) { + Free(record.memory, record.base, function); + record.base = nullptr; + } + record.cursor = nullptr; + record.limit = nullptr; +} + +template +std::uint64_t Get(GuestStreamRec* stream, const char* function) { + auto& record = Stream(stream, function); + auto* cursor = record.cursor; + std::uint64_t value = 0; + if (Address(cursor) + (Bytes - 1) < Address(record.limit)) { + if (!cursor) Unsupported(function, "null frame cursor"); + value = Little ? LittleEndian(cursor, Bytes) : BigEndian(cursor, Bytes); + cursor += Bytes; + } + record.cursor = cursor; + return value; +} + +template +std::uint64_t Read(GuestStreamRec* stream, int* error, const char* function) { + auto& record = Stream(stream, function); + if (!error) Unsupported(function, "null error"); + *error = 0; + if (record.pos + (Bytes - 1) >= record.size) { + *error = InvalidStreamOperation; + return 0; + } + std::uint8_t reads[Bytes]{}; + const std::uint8_t* bytes; + if (record.read) { + if (record.read(stream, record.pos, reads, Bytes) != Bytes) { + *error = InvalidStreamOperation; + return 0; + } + bytes = reads; + } else { + bytes = Offset(record.base, record.pos); + if (bytes && !record.base) Unsupported(function, "null memory stream base"); + } + std::uint64_t value = 0; + if (bytes) value = Little ? LittleEndian(bytes, Bytes) : BigEndian(bytes, Bytes); + record.pos += Bytes; + return value; +} + +template +void Store(std::uint8_t* target, std::uint64_t value) { + const auto stored = static_cast(value); + std::memcpy(target, &stored, sizeof(stored)); +} + +} + +extern "C" { + +void APS5_VABI FT_Stream_OpenMemory_nid_postfix(GuestStreamRec* stream, const std::uint8_t* base, std::uint64_t size) { + auto& record = Stream(stream, __func__); + record.base = const_cast(base); + record.size = size; + record.pos = 0; + record.cursor = nullptr; + record.read = nullptr; + record.close = nullptr; +} + +void APS5_VABI FT_Stream_Close_nid_postfix(GuestStreamRec* stream) { + if (stream && stream->close) stream->close(stream); +} + +int APS5_VABI FT_Stream_Seek_nid_postfix(GuestStreamRec* stream, std::uint64_t pos) { + return Seek(stream, pos, __func__); +} + +int APS5_VABI FT_Stream_Skip_nid_postfix(GuestStreamRec* stream, std::int64_t distance) { + if (distance < 0) return InvalidStreamOperation; + const auto& record = Stream(stream, __func__); + return Seek(stream, record.pos + static_cast(distance), __func__); +} + +std::int64_t APS5_VABI FT_Stream_Pos_nid_postfix(GuestStreamRec* stream) { + return static_cast(Stream(stream, __func__).pos); +} + +int APS5_VABI FT_Stream_Read_nid_postfix(GuestStreamRec* stream, std::uint8_t* buffer, std::uint64_t count) { + return ReadAt(stream, Stream(stream, __func__).pos, buffer, count, __func__); +} + +int APS5_VABI FT_Stream_ReadAt_nid_postfix(GuestStreamRec* stream, std::uint64_t pos, std::uint8_t* buffer, std::uint64_t count) { + return ReadAt(stream, pos, buffer, count, __func__); +} + +std::uint64_t APS5_VABI FT_Stream_TryRead_nid_postfix(GuestStreamRec* stream, std::uint8_t* buffer, std::uint64_t count) { + auto& record = Stream(stream, __func__); + if (record.pos >= record.size) return 0; + std::uint64_t readBytes; + if (record.read) { + readBytes = record.read(stream, record.pos, buffer, count); + } else { + readBytes = record.size - record.pos; + if (readBytes > count) readBytes = count; + CopyFromBase(buffer, record, record.pos, readBytes, __func__); + } + record.pos += readBytes; + return readBytes; +} + +int APS5_VABI FT_Stream_EnterFrame_nid_postfix(GuestStreamRec* stream, std::uint64_t count) { + return EnterFrame(stream, count, __func__); +} + +void APS5_VABI FT_Stream_ExitFrame_nid_postfix(GuestStreamRec* stream) { + ExitFrame(stream, __func__); +} + +int APS5_VABI FT_Stream_ExtractFrame_nid_postfix(GuestStreamRec* stream, std::uint64_t count, std::uint8_t** bytes) { + if (!bytes) Unsupported(__func__, "null frame pointer"); + const int error = EnterFrame(stream, count, __func__); + if (!error) { + *bytes = stream->cursor; + stream->cursor = nullptr; + stream->limit = nullptr; + } + return error; +} + +void APS5_VABI FT_Stream_ReleaseFrame_nid_postfix(GuestStreamRec* stream, std::uint8_t** bytes) { + if (!bytes) Unsupported(__func__, "null frame pointer"); + if (stream && stream->read) Free(stream->memory, *bytes, __func__); + *bytes = nullptr; +} + +std::int8_t APS5_VABI FT_Stream_GetChar_nid_postfix(GuestStreamRec* stream) { + auto& record = Stream(stream, __func__); + std::int8_t value = 0; + if (Address(record.cursor) < Address(record.limit)) { + if (!record.cursor) Unsupported(__func__, "null frame cursor"); + value = static_cast(*record.cursor); + record.cursor += 1; + } + return value; +} + +std::int16_t APS5_VABI FT_Stream_GetShort_nid_postfix(GuestStreamRec* stream) { + return static_cast(Get<2, false>(stream, __func__)); +} + +std::int16_t APS5_VABI FT_Stream_GetShortLE_nid_postfix(GuestStreamRec* stream) { + return static_cast(Get<2, true>(stream, __func__)); +} + +std::uint16_t APS5_VABI FT_Stream_GetUShort_nid_postfix(GuestStreamRec* stream) { + return static_cast(Get<2, false>(stream, __func__)); +} + +std::uint16_t APS5_VABI FT_Stream_GetUShortLE_nid_postfix(GuestStreamRec* stream) { + return static_cast(Get<2, true>(stream, __func__)); +} + +std::int64_t APS5_VABI FT_Stream_GetOffset_nid_postfix(GuestStreamRec* stream) { + return SignExtend(Get<3, false>(stream, __func__), 3); +} + +std::uint64_t APS5_VABI FT_Stream_GetUOffset_nid_postfix(GuestStreamRec* stream) { + return Get<3, false>(stream, __func__); +} + +std::int64_t APS5_VABI FT_Stream_GetLong_nid_postfix(GuestStreamRec* stream) { + return SignExtend(Get<4, false>(stream, __func__), 4); +} + +std::int64_t APS5_VABI FT_Stream_GetLongLE_nid_postfix(GuestStreamRec* stream) { + return SignExtend(Get<4, true>(stream, __func__), 4); +} + +std::uint64_t APS5_VABI FT_Stream_GetULong_nid_postfix(GuestStreamRec* stream) { + return Get<4, false>(stream, __func__); +} + +std::uint64_t APS5_VABI FT_Stream_GetULongLE_nid_postfix(GuestStreamRec* stream) { + return Get<4, true>(stream, __func__); +} + +std::int8_t APS5_VABI FT_Stream_ReadChar_nid_postfix(GuestStreamRec* stream, int* error) { + auto& record = Stream(stream, __func__); + if (!error) Unsupported(__func__, "null error"); + *error = 0; + std::uint8_t value = 0; + if (record.read) { + if (record.read(stream, record.pos, &value, 1) != 1) { + *error = InvalidStreamOperation; + return 0; + } + } else { + if (record.pos >= record.size) { + *error = InvalidStreamOperation; + return 0; + } + if (!record.base) Unsupported(__func__, "null memory stream base"); + value = record.base[record.pos]; + } + record.pos += 1; + return static_cast(value); +} + +std::int16_t APS5_VABI FT_Stream_ReadShort_nid_postfix(GuestStreamRec* stream, int* error) { + return static_cast(Read<2, false>(stream, error, __func__)); +} + +std::int16_t APS5_VABI FT_Stream_ReadShortLE_nid_postfix(GuestStreamRec* stream, int* error) { + return static_cast(Read<2, true>(stream, error, __func__)); +} + +std::uint16_t APS5_VABI FT_Stream_ReadUShort_nid_postfix(GuestStreamRec* stream, int* error) { + return static_cast(Read<2, false>(stream, error, __func__)); +} + +std::uint16_t APS5_VABI FT_Stream_ReadUShortLE_nid_postfix(GuestStreamRec* stream, int* error) { + return static_cast(Read<2, true>(stream, error, __func__)); +} + +std::int64_t APS5_VABI FT_Stream_ReadOffset_nid_postfix(GuestStreamRec* stream, int* error) { + return SignExtend(Read<3, false>(stream, error, __func__), 3); +} + +std::uint64_t APS5_VABI FT_Stream_ReadUOffset_nid_postfix(GuestStreamRec* stream, int* error) { + return Read<3, false>(stream, error, __func__); +} + +std::int64_t APS5_VABI FT_Stream_ReadLong_nid_postfix(GuestStreamRec* stream, int* error) { + return SignExtend(Read<4, false>(stream, error, __func__), 4); +} + +std::int64_t APS5_VABI FT_Stream_ReadLongLE_nid_postfix(GuestStreamRec* stream, int* error) { + return SignExtend(Read<4, true>(stream, error, __func__), 4); +} + +std::uint64_t APS5_VABI FT_Stream_ReadULong_nid_postfix(GuestStreamRec* stream, int* error) { + return Read<4, false>(stream, error, __func__); +} + +std::uint64_t APS5_VABI FT_Stream_ReadULongLE_nid_postfix(GuestStreamRec* stream, int* error) { + return Read<4, true>(stream, error, __func__); +} + +int APS5_VABI FT_Stream_ReadFields_nid_postfix(GuestStreamRec* stream, const GuestFrameField* fields, void* structure) { + const char* function = __func__; + if (!fields) return InvalidArgument; + auto& record = Stream(stream, function); + int error = 0; + bool frameAccessed = false; + auto* cursor = record.cursor; + const auto finish = [&] { + if (frameAccessed) ExitFrame(stream, function); + return error; + }; + const auto refuse = [&](const char* what) { + if (frameAccessed) ExitFrame(stream, function); + Unsupported(function, what); + }; + for (;; ++fields) { + const GuestFrameField field = *fields; + if (field.value == FrameStart) { + error = EnterFrame(stream, field.offset, function); + if (error) return finish(); + frameAccessed = true; + cursor = record.cursor; + continue; + } + if (field.value == FrameBytes || field.value == FrameSkip) { + const std::uint64_t length = field.size; + if (Address(cursor) + length > Address(record.limit)) { + error = InvalidStreamOperation; + return finish(); + } + if (field.value == FrameBytes && length != 0) { + if (!structure) refuse("null structure"); + if (!cursor) refuse("null frame cursor"); + std::memmove(static_cast(structure) + field.offset, cursor, length); + } + cursor = Offset(cursor, length); + continue; + } + unsigned bytes = 0; + bool little = false; + switch (field.value) { + case 8: case 9: bytes = 1; break; + case 12: case 13: bytes = 2; break; + case 14: case 15: bytes = 2; little = true; break; + case 16: case 17: bytes = 4; break; + case 18: case 19: bytes = 4; little = true; break; + case 20: case 21: bytes = 3; break; + case 22: case 23: bytes = 3; little = true; break; + default: + record.cursor = cursor; + return finish(); + } + if (!cursor) refuse("null frame cursor"); + std::uint64_t value = little ? LittleEndian(cursor, bytes) : BigEndian(cursor, bytes); + cursor += bytes; + if (field.value & FrameSigned) { + const unsigned shift = 32 - bytes * 8; + value = static_cast(static_cast(static_cast(static_cast(value << shift)) >> shift)); + } + if (!structure) refuse("null structure"); + auto* target = static_cast(structure) + field.offset; + switch (field.size) { + case 1: Store(target, value); break; + case 2: Store(target, value); break; + case 4: Store(target, value); break; + default: Store(target, value); break; + } + } +} + +void APS5_VABI FT_Stream_Free_nid_postfix(GuestStreamRec* stream, std::int32_t external) { + if (!stream) return; + auto* memory = stream->memory; + if (!external && (!memory || !memory->free)) Unsupported(__func__, "null memory release"); + FT_Stream_Close_nid_postfix(stream); + if (!external) Free(memory, stream, __func__); +} + +} diff --git a/core/libs/prx/libSceFreeTypeOt/include/Stream.hpp b/core/libs/prx/libSceFreeTypeOt/include/Stream.hpp new file mode 100644 index 0000000000..03015f80b5 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/include/Stream.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "prx/libSceFreeType/include/GuestList.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +namespace FreeType { + +struct GuestStreamRec; + +using GuestStreamIoFunc = std::uint64_t (APS5_VABI *)(GuestStreamRec*, std::uint64_t, std::uint8_t*, std::uint64_t); +using GuestStreamCloseFunc = void (APS5_VABI *)(GuestStreamRec*); + +struct GuestStreamRec { + std::uint8_t* base; + std::uint64_t size; + std::uint64_t pos; + void* descriptor; + void* pathname; + GuestStreamIoFunc read; + GuestStreamCloseFunc close; + GuestMemoryRec* memory; + std::uint8_t* cursor; + std::uint8_t* limit; +}; + +struct GuestFrameField { + std::uint8_t value; + std::uint8_t size; + std::uint16_t offset; +}; + +static_assert(sizeof(GuestStreamRec) == 80 && offsetof(GuestStreamRec, size) == 8 && offsetof(GuestStreamRec, pos) == 16); +static_assert(offsetof(GuestStreamRec, read) == 40 && offsetof(GuestStreamRec, close) == 48 && offsetof(GuestStreamRec, memory) == 56); +static_assert(offsetof(GuestStreamRec, cursor) == 64 && offsetof(GuestStreamRec, limit) == 72); +static_assert(sizeof(GuestFrameField) == 4 && offsetof(GuestFrameField, size) == 1 && offsetof(GuestFrameField, offset) == 2); + +} + +extern "C" { + +void APS5_VABI FT_Stream_OpenMemory_nid_postfix(FreeType::GuestStreamRec* stream, const std::uint8_t* base, std::uint64_t size); +void APS5_VABI FT_Stream_Close_nid_postfix(FreeType::GuestStreamRec* stream); +int APS5_VABI FT_Stream_Seek_nid_postfix(FreeType::GuestStreamRec* stream, std::uint64_t pos); +int APS5_VABI FT_Stream_Skip_nid_postfix(FreeType::GuestStreamRec* stream, std::int64_t distance); +std::int64_t APS5_VABI FT_Stream_Pos_nid_postfix(FreeType::GuestStreamRec* stream); +int APS5_VABI FT_Stream_Read_nid_postfix(FreeType::GuestStreamRec* stream, std::uint8_t* buffer, std::uint64_t count); +int APS5_VABI FT_Stream_ReadAt_nid_postfix(FreeType::GuestStreamRec* stream, std::uint64_t pos, std::uint8_t* buffer, std::uint64_t count); +std::uint64_t APS5_VABI FT_Stream_TryRead_nid_postfix(FreeType::GuestStreamRec* stream, std::uint8_t* buffer, std::uint64_t count); +int APS5_VABI FT_Stream_EnterFrame_nid_postfix(FreeType::GuestStreamRec* stream, std::uint64_t count); +void APS5_VABI FT_Stream_ExitFrame_nid_postfix(FreeType::GuestStreamRec* stream); +int APS5_VABI FT_Stream_ExtractFrame_nid_postfix(FreeType::GuestStreamRec* stream, std::uint64_t count, std::uint8_t** bytes); +void APS5_VABI FT_Stream_ReleaseFrame_nid_postfix(FreeType::GuestStreamRec* stream, std::uint8_t** bytes); +std::int8_t APS5_VABI FT_Stream_GetChar_nid_postfix(FreeType::GuestStreamRec* stream); +std::int16_t APS5_VABI FT_Stream_GetShort_nid_postfix(FreeType::GuestStreamRec* stream); +std::int16_t APS5_VABI FT_Stream_GetShortLE_nid_postfix(FreeType::GuestStreamRec* stream); +std::uint16_t APS5_VABI FT_Stream_GetUShort_nid_postfix(FreeType::GuestStreamRec* stream); +std::uint16_t APS5_VABI FT_Stream_GetUShortLE_nid_postfix(FreeType::GuestStreamRec* stream); +std::int64_t APS5_VABI FT_Stream_GetOffset_nid_postfix(FreeType::GuestStreamRec* stream); +std::uint64_t APS5_VABI FT_Stream_GetUOffset_nid_postfix(FreeType::GuestStreamRec* stream); +std::int64_t APS5_VABI FT_Stream_GetLong_nid_postfix(FreeType::GuestStreamRec* stream); +std::int64_t APS5_VABI FT_Stream_GetLongLE_nid_postfix(FreeType::GuestStreamRec* stream); +std::uint64_t APS5_VABI FT_Stream_GetULong_nid_postfix(FreeType::GuestStreamRec* stream); +std::uint64_t APS5_VABI FT_Stream_GetULongLE_nid_postfix(FreeType::GuestStreamRec* stream); +std::int8_t APS5_VABI FT_Stream_ReadChar_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::int16_t APS5_VABI FT_Stream_ReadShort_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::int16_t APS5_VABI FT_Stream_ReadShortLE_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::uint16_t APS5_VABI FT_Stream_ReadUShort_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::uint16_t APS5_VABI FT_Stream_ReadUShortLE_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::int64_t APS5_VABI FT_Stream_ReadOffset_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::uint64_t APS5_VABI FT_Stream_ReadUOffset_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::int64_t APS5_VABI FT_Stream_ReadLong_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::int64_t APS5_VABI FT_Stream_ReadLongLE_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::uint64_t APS5_VABI FT_Stream_ReadULong_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +std::uint64_t APS5_VABI FT_Stream_ReadULongLE_nid_postfix(FreeType::GuestStreamRec* stream, int* error); +int APS5_VABI FT_Stream_ReadFields_nid_postfix(FreeType::GuestStreamRec* stream, const FreeType::GuestFrameField* fields, void* structure); +void APS5_VABI FT_Stream_Free_nid_postfix(FreeType::GuestStreamRec* stream, std::int32_t external); + +} diff --git a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp index 780b9b9fa8..00e2da7277 100644 --- a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp +++ b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp @@ -1,4 +1,5 @@ #pragma once +#include "prx/libSceFreeTypeOt/include/Stream.hpp" #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeTypeOt/include/Calc.hpp" diff --git a/core/libs/tests/GuestFreeTypeStream.cpp b/core/libs/tests/GuestFreeTypeStream.cpp new file mode 100644 index 0000000000..2958347fe7 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeStream.cpp @@ -0,0 +1,503 @@ +#include "prx/libSceFreeTypeOt/include/Stream.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +using FreeType::GuestFrameField; +using FreeType::GuestMemoryRec; +using FreeType::GuestStreamRec; + +#define STREAM_EXPORTS(X) \ + X(OpenMemory, "a7RlARfexoo") X(Close, "gVf7xRLdWm8") X(Seek, "R1mZutnlTiM") X(Skip, "4J4Baf8ana8") \ + X(Pos, "jX1zq1i8KfI") X(Read, "U+0WKVAePww") X(ReadAt, "dlbnv6Wass8") X(TryRead, "Az4H9FlfxCA") \ + X(EnterFrame, "FHdeCFVf-f8") X(ExitFrame, "Qmgc0XACzRg") X(ExtractFrame, "0rGx-oFMLyk") X(ReleaseFrame, "xejRyEbsLcw") \ + X(GetChar, "jl0VxYZb8-o") X(GetShort, "dXJK59vXRxs") X(GetShortLE, "BB4gl7SgLdk") X(GetUShort, "u6Xquj1bFwc") \ + X(GetUShortLE, "-Xm1cLEpBec") X(GetOffset, "tDssQCH2syY") X(GetUOffset, "4yt9cD1qcEI") X(GetLong, "Er0Lin-u20o") \ + X(GetLongLE, "KHj92a3WDn4") X(GetULong, "Fv8MWCUH8k8") X(GetULongLE, "RzgDJTjQst4") X(ReadChar, "cmd41395g7Y") \ + X(ReadShort, "mF0B64O954g") X(ReadShortLE, "c002A4zTOgA") X(ReadUShort, "6a1p3QgXvo0") X(ReadUShortLE, "JFSCOXTEvfg") \ + X(ReadOffset, "1z6bBg1qBfU") X(ReadUOffset, "S+n0Md8Rjfw") X(ReadLong, "Bmi8PdvsM3M") X(ReadLongLE, "jJh4SMt8NFQ") \ + X(ReadULong, "mcT796Qgj+g") X(ReadULongLE, "b5gZp-r3WWI") X(ReadFields, "SX5I+XzH8HI") X(Free, "thA48N-0VwQ") + +namespace { + +#define DECLARE(name, nid) decltype(&FT_Stream_##name##_nid_postfix) name = nullptr; +struct Api { + STREAM_EXPORTS(DECLARE) +}; +#undef DECLARE + +Api api; + +constexpr int InvalidArgument = 0x06; +constexpr int OutOfMemory = 0x40; +constexpr int InvalidStreamOperation = 0x55; +constexpr auto Max = std::numeric_limits::max(); + +void Require(bool result, const char* message) { + if (result) return; + std::fprintf(stderr, "FreeType stream: %s\n", message); + std::exit(1); +} + +template +bool Throws(Call call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +std::uint8_t* At(std::uint64_t address) { + return reinterpret_cast(static_cast(address)); +} + +struct ReadCall { + GuestStreamRec* stream; + std::uint64_t offset; + std::uint8_t* buffer; + std::uint64_t count; +}; + +struct Device { + std::vector source; + std::vector calls; + std::uint64_t seekResult = 0; + std::uint64_t shortBy = 0; + unsigned closes = 0; + std::vector allocations; + std::vector releases; + bool failAllocation = false; + std::vector order; +}; + +Device device; + +std::uint64_t APS5_VABI DeviceRead(GuestStreamRec* stream, std::uint64_t offset, std::uint8_t* buffer, std::uint64_t count) { + device.calls.push_back({stream, offset, buffer, count}); + if (!buffer && count == 0) return device.seekResult; + std::uint64_t available = offset < device.source.size() ? device.source.size() - offset : 0; + if (available > count) available = count; + available = available > device.shortBy ? available - device.shortBy : 0; + if (available) std::memcpy(buffer, device.source.data() + offset, available); + return available; +} + +void APS5_VABI DeviceClose(GuestStreamRec* stream) { + ++device.closes; + device.order.push_back('c'); + stream->memory = nullptr; +} + +void* APS5_VABI DeviceAlloc(GuestMemoryRec*, std::int64_t size) { + device.allocations.push_back(size); + return device.failAllocation ? nullptr : std::malloc(static_cast(size)); +} + +void APS5_VABI DeviceRelease(GuestMemoryRec*, void* block) { + device.releases.push_back(block); + device.order.push_back('f'); + std::free(block); +} + +GuestMemoryRec memory{nullptr, DeviceAlloc, DeviceRelease, nullptr}; + +GuestStreamRec MemoryStream(std::uint8_t* base, std::uint64_t size) { + GuestStreamRec stream{}; + api.OpenMemory(&stream, base, size); + return stream; +} + +GuestStreamRec DeviceStream(std::vector source, std::uint64_t size) { + device = {}; + device.source = std::move(source); + GuestStreamRec stream{}; + stream.size = size; + stream.read = DeviceRead; + stream.memory = &memory; + return stream; +} + +void TestOpenMemoryAndPosition() { + std::array data{{0x80, 0x01, 0x02, 0x03, 0xFE, 0xFF, 0x7F, 0x10}}; + GuestStreamRec stream; + std::memset(&stream, 0xCD, sizeof(stream)); + stream.descriptor = At(0x11); + stream.pathname = At(0x22); + stream.memory = reinterpret_cast(At(0x33)); + stream.limit = At(0x44); + api.OpenMemory(&stream, data.data(), data.size()); + Require(stream.base == data.data() && stream.size == 8 && stream.pos == 0, "open memory sets base, size and position"); + Require(!stream.cursor && !stream.read && !stream.close, "open memory clears cursor and callbacks"); + Require(stream.limit == At(0x44) && stream.memory == reinterpret_cast(At(0x33)), "open memory keeps limit and memory"); + Require(stream.descriptor == At(0x11) && stream.pathname == At(0x22), "open memory keeps descriptor and path name"); + Require(api.Pos(&stream) == 0, "initial position"); + Require(api.Seek(&stream, 8) == 0 && stream.pos == 8, "seek to the end is valid"); + Require(api.Seek(&stream, 9) == InvalidStreamOperation && stream.pos == 8, "seek past the end fails and keeps the position"); + Require(api.Skip(nullptr, -1) == InvalidStreamOperation, "negative skip fails before reading the stream"); + stream.pos = 3; + Require(api.Skip(&stream, -1) == InvalidStreamOperation && stream.pos == 3, "negative skip keeps the position"); + Require(api.Skip(&stream, 5) == 0 && stream.pos == 8, "skip to the end"); + Require(api.Skip(&stream, std::numeric_limits::max()) == InvalidStreamOperation && stream.pos == 8, "skip past the end"); + stream.pos = Max - 1; + Require(api.Skip(&stream, 3) == 0 && stream.pos == 1, "skip wraps as unsigned long"); + stream.pos = Max; + Require(api.Pos(&stream) == -1, "position is returned as a signed long"); + Require(api.Seek(&stream, 0) == 0 && api.Pos(&stream) == 0, "seek to the start"); +} + +void TestMemoryReads() { + std::array data{{0x80, 0x01, 0x02, 0x03, 0xFE, 0xFF, 0x7F, 0x10}}; + auto stream = MemoryStream(data.data(), data.size()); + std::array out{}; + Require(api.ReadAt(&stream, 2, out.data(), 3) == 0 && stream.pos == 5, "read at an offset"); + Require(out[0] == 0x02 && out[1] == 0x03 && out[2] == 0xFE, "read-at bytes"); + out.fill(0); + Require(api.ReadAt(&stream, 6, out.data(), 4) == InvalidStreamOperation && stream.pos == 8, "short read copies what remains and fails"); + Require(out[0] == 0x7F && out[1] == 0x10 && out[2] == 0, "short read bytes"); + stream.pos = 4; + Require(api.ReadAt(&stream, 8, out.data(), 1) == InvalidStreamOperation && stream.pos == 4, "read at the end fails without moving"); + Require(api.ReadAt(&stream, 0, nullptr, 0) == 0 && stream.pos == 0, "empty read accepts a null buffer"); + Require(Throws([&] { api.ReadAt(&stream, 0, nullptr, 1); }), "null buffer with bytes to copy is unsupported"); + stream.pos = 1; + out.fill(0); + Require(api.Read(&stream, out.data(), 2) == 0 && stream.pos == 3 && out[0] == 0x01 && out[1] == 0x02, "read at the current position"); + stream.pos = 6; + Require(api.TryRead(&stream, out.data(), 5) == 2 && stream.pos == 8 && out[0] == 0x7F, "try-read returns the bytes available"); + Require(api.TryRead(&stream, out.data(), 5) == 0 && stream.pos == 8, "try-read at the end"); + stream.pos = 1; + Require(api.EnterFrame(&stream, 4) == 0, "enter a frame"); + Require(stream.cursor == data.data() + 1 && stream.limit == data.data() + 5 && stream.pos == 5, "memory frame points into the stream"); + Require(api.GetUShort(&stream) == 0x0102 && api.GetChar(&stream) == 3 && api.GetChar(&stream) == -2, "frame values"); + Require(api.GetChar(&stream) == 0 && api.GetUShort(&stream) == 0 && stream.cursor == data.data() + 5, "reads at the frame end return 0 and keep the cursor"); + api.ExitFrame(&stream); + Require(!stream.cursor && !stream.limit && stream.base == data.data(), "exit keeps a memory stream's base"); + stream.pos = 8; + Require(api.EnterFrame(&stream, 0) == InvalidStreamOperation, "frame at the end fails"); + stream.pos = 6; + Require(api.EnterFrame(&stream, 3) == InvalidStreamOperation && stream.pos == 6 && !stream.cursor, "frame past the end fails"); + stream.pos = 0; + Require(api.EnterFrame(&stream, 0) == 0 && stream.cursor == data.data() && stream.limit == data.data() && stream.pos == 0, "empty frame"); + api.ExitFrame(&stream); + std::uint8_t* frame = nullptr; + stream.pos = 2; + Require(api.ExtractFrame(&stream, 3, &frame) == 0 && frame == data.data() + 2 && !stream.cursor && !stream.limit && stream.pos == 5, "extract a memory frame"); + api.ReleaseFrame(&stream, &frame); + Require(!frame, "release a memory frame"); + frame = At(0x99); + Require(api.ExtractFrame(&stream, 4, &frame) == InvalidStreamOperation && frame == At(0x99), "failed extraction leaves the pointer"); + api.ReleaseFrame(nullptr, &frame); + Require(!frame, "release without a stream clears the pointer"); + stream.pos = 1; + Require(Throws([&] { api.ExtractFrame(&stream, 3, nullptr); }), "null memory frame pointer is unsupported"); + Require(stream.pos == 1 && !stream.cursor && !stream.limit, "null memory frame pointer enters no frame"); +} + +void TestFrameGetters() { + std::array data{{0x80, 0x01, 0x02, 0x03}}; + GuestStreamRec stream{}; + const auto frame = [&](unsigned length) { + stream.cursor = data.data(); + stream.limit = data.data() + length; + }; + frame(4); + Require(api.GetShort(&stream) == -32767 && stream.cursor == data.data() + 2, "signed big-endian short"); + frame(4); + Require(api.GetShortLE(&stream) == 0x0180, "signed little-endian short"); + frame(4); + Require(api.GetUShort(&stream) == 0x8001, "unsigned big-endian short"); + frame(4); + Require(api.GetUShortLE(&stream) == 0x0180, "unsigned little-endian short"); + frame(4); + Require(api.GetOffset(&stream) == 0x800102 - 0x1000000 && stream.cursor == data.data() + 3, "signed 24-bit offset"); + frame(4); + Require(api.GetUOffset(&stream) == 0x800102, "unsigned 24-bit offset"); + frame(4); + Require(api.GetLong(&stream) == static_cast(0x80010203u), "signed big-endian long"); + frame(4); + Require(api.GetLongLE(&stream) == 0x03020180, "signed little-endian long"); + frame(4); + Require(api.GetULong(&stream) == 0x80010203u && stream.cursor == data.data() + 4, "unsigned big-endian long"); + frame(4); + Require(api.GetULongLE(&stream) == 0x03020180u, "unsigned little-endian long"); + frame(1); + Require(api.GetUShort(&stream) == 0 && stream.cursor == data.data(), "short past the frame"); + frame(2); + Require(api.GetUOffset(&stream) == 0 && api.GetULong(&stream) == 0 && stream.cursor == data.data(), "offset and long past the frame"); + Require(api.GetChar(&stream) == -128, "signed char"); + stream.cursor = nullptr; + stream.limit = nullptr; + Require(api.GetChar(&stream) == 0 && api.GetULong(&stream) == 0 && !stream.cursor, "no frame"); + stream.limit = At(8); + Require(Throws([&] { api.GetChar(&stream); }) && Throws([&] { api.GetUShort(&stream); }), "null cursor below the limit is unsupported"); +} + +void TestMemoryReaders() { + std::array data{{0x80, 0x01, 0x02, 0x03}}; + auto stream = MemoryStream(data.data(), data.size()); + int error = -1; + const auto read = [&](auto function, std::uint64_t pos) { + stream.pos = pos; + error = -1; + return function(&stream, &error); + }; + Require(read(api.ReadShort, 0) == -32767 && error == 0 && stream.pos == 2, "read signed short"); + Require(read(api.ReadShortLE, 0) == 0x0180 && stream.pos == 2, "read signed little-endian short"); + Require(read(api.ReadUShort, 2) == 0x0203 && stream.pos == 4, "read unsigned short"); + Require(read(api.ReadUShortLE, 2) == 0x0302 && error == 0, "read unsigned little-endian short"); + Require(read(api.ReadOffset, 0) == 0x800102 - 0x1000000 && stream.pos == 3, "read signed offset"); + Require(read(api.ReadUOffset, 1) == 0x010203 && stream.pos == 4, "read unsigned offset"); + Require(read(api.ReadLong, 0) == static_cast(0x80010203u), "read signed long"); + Require(read(api.ReadLongLE, 0) == 0x03020180, "read signed little-endian long"); + Require(read(api.ReadULong, 0) == 0x80010203u && stream.pos == 4, "read unsigned long"); + Require(read(api.ReadULongLE, 0) == 0x03020180u, "read unsigned little-endian long"); + Require(read(api.ReadChar, 0) == -128 && error == 0 && stream.pos == 1, "read char"); + Require(read(api.ReadChar, 4) == 0 && error == InvalidStreamOperation && stream.pos == 4, "read char at the end"); + Require(read(api.ReadULong, 1) == 0 && error == InvalidStreamOperation && stream.pos == 1, "long past the end"); + Require(read(api.ReadUShort, 3) == 0 && error == InvalidStreamOperation && stream.pos == 3, "short past the end"); + std::array window{{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}; + stream = MemoryStream(window.data() + 1, 4); + Require(read(api.ReadUShort, Max) == 0x1122 && error == 0 && stream.pos == 1, "position plus size wraps as unsigned long"); + Require(read(api.ReadChar, Max) == 0 && error == InvalidStreamOperation, "char at the maximum position"); + stream = MemoryStream(nullptr, 4); + Require(read(api.ReadUShort, 0) == 0 && error == 0 && stream.pos == 2, "null base at position 0 reads 0"); + Require(Throws([&] { read(api.ReadUShort, 2); }), "null base past position 0 is unsupported"); + Require(Throws([&] { read(api.ReadChar, 0); }), "null base char is unsupported"); + Require(Throws([&] { api.ReadULong(&stream, nullptr); }), "null error pointer is unsupported"); +} + +void TestDeviceStream() { + auto stream = DeviceStream({1, 2, 3, 4, 5, 6, 7, 8}, 8); + Require(api.Seek(&stream, 1000) == 0 && stream.pos == 1000, "device seek does not check the size"); + Require(device.calls.size() == 1 && device.calls[0].stream == &stream && device.calls[0].offset == 1000 && !device.calls[0].buffer && device.calls[0].count == 0, "seek calls read with no buffer"); + device.seekResult = 1; + Require(api.Seek(&stream, 2) == InvalidStreamOperation && stream.pos == 1000, "failed device seek keeps the position"); + device.seekResult = 0; + std::array out{}; + device.calls.clear(); + Require(api.ReadAt(&stream, 8, out.data(), 1) == InvalidStreamOperation && device.calls.empty(), "read at the end does not call the device"); + Require(api.ReadAt(&stream, 2, out.data(), 3) == 0 && stream.pos == 5 && out[0] == 3 && out[2] == 5, "device read"); + Require(device.calls.size() == 1 && device.calls[0].offset == 2 && device.calls[0].buffer == out.data() && device.calls[0].count == 3, "device read arguments"); + device.shortBy = 1; + Require(api.ReadAt(&stream, 4, out.data(), 3) == InvalidStreamOperation && stream.pos == 6, "short device read"); + stream.pos = 6; + Require(api.TryRead(&stream, out.data(), 4) == 1 && stream.pos == 7, "device try-read"); + device.shortBy = 0; + device.calls.clear(); + stream.pos = 8; + Require(api.TryRead(&stream, out.data(), 4) == 0 && device.calls.empty(), "try-read at the end does not call the device"); + stream.pos = 2; + Require(api.EnterFrame(&stream, 4) == 0 && device.allocations.size() == 1 && device.allocations[0] == 4, "device frame allocates its size"); + auto* block = stream.base; + Require(block && stream.cursor == block && stream.limit == block + 4 && stream.pos == 6, "device frame"); + Require(device.calls.back().offset == 2 && device.calls.back().buffer == block && device.calls.back().count == 4, "device frame read"); + Require(api.GetULong(&stream) == 0x03040506u, "device frame value"); + api.ExitFrame(&stream); + Require(device.releases.size() == 1 && device.releases[0] == block && !stream.base && !stream.cursor && !stream.limit, "exit releases a device frame"); + device.allocations.clear(); + Require(api.EnterFrame(&stream, 9) == InvalidStreamOperation && device.allocations.empty(), "frame larger than the stream"); + device.failAllocation = true; + stream.base = At(0x77); + Require(api.EnterFrame(&stream, 2) == OutOfMemory && !stream.base && stream.pos == 6, "allocation failure"); + device.failAllocation = false; + device.releases.clear(); + stream.pos = 6; + Require(api.EnterFrame(&stream, 4) == InvalidStreamOperation, "short frame read fails"); + Require(device.releases.size() == 1 && !stream.base && !stream.cursor && stream.limit == At(4) && stream.pos == 8, "short frame read releases the block"); + stream.memory = nullptr; + device.calls.clear(); + Require(api.EnterFrame(&stream, 0) == 0 && !stream.base && !stream.cursor && !stream.limit, "empty device frame allocates nothing"); + Require(device.calls.size() == 1 && !device.calls[0].buffer && device.calls[0].count == 0, "empty device frame reads nothing"); + api.ExitFrame(&stream); + stream.memory = &memory; + stream.size = Max; + device.allocations.clear(); + stream.base = At(0x77); + Require(api.EnterFrame(&stream, std::uint64_t{1} << 63) == InvalidArgument && !stream.base && device.allocations.empty(), "frame size above LONG_MAX"); + stream.size = 8; + stream.pos = 0; + std::uint8_t* frame = nullptr; + Require(api.ExtractFrame(&stream, 2, &frame) == 0 && frame && frame == stream.base && !stream.cursor && !stream.limit, "extract a device frame"); + device.releases.clear(); + api.ReleaseFrame(&stream, &frame); + Require(!frame && device.releases.size() == 1, "release a device frame"); + Require(Throws([&] { api.ReleaseFrame(&stream, nullptr); }), "null frame pointer is unsupported"); + device.allocations.clear(); + device.calls.clear(); + device.releases.clear(); + stream.base = At(0x55); + stream.pos = 1; + Require(Throws([&] { api.ExtractFrame(&stream, 2, nullptr); }), "null device frame pointer is unsupported"); + Require(device.allocations.empty() && device.calls.empty() && device.releases.empty(), "null device frame pointer allocates and reads nothing"); + Require(stream.base == At(0x55) && stream.pos == 1 && !stream.cursor && !stream.limit, "null device frame pointer leaves the stream"); + GuestMemoryRec noRelease{nullptr, DeviceAlloc, nullptr, nullptr}; + stream.memory = &noRelease; + Require(Throws([&] { api.EnterFrame(&stream, 2); }), "allocator without release is unsupported"); + Require(device.allocations.empty() && device.calls.empty() && stream.base == At(0x55) && stream.pos == 1, "allocator without release allocates and reads nothing"); + Require(api.EnterFrame(&stream, 9) == InvalidStreamOperation && device.calls.empty(), "frame larger than the stream does not need the allocator"); + Require(api.EnterFrame(&stream, 0) == 0 && device.allocations.empty() && !stream.base, "empty frame does not need the allocator"); + api.ExitFrame(&stream); + stream.memory = &memory; + int error = -1; + stream.base = nullptr; + stream.pos = 0; + device.calls.clear(); + Require(api.ReadULong(&stream, &error) == 0x01020304u && error == 0 && stream.pos == 4, "device long"); + Require(device.calls.size() == 1 && device.calls[0].offset == 0 && device.calls[0].count == 4, "device long arguments"); + device.shortBy = 1; + Require(api.ReadUShortLE(&stream, &error) == 0 && error == InvalidStreamOperation && stream.pos == 4, "short device read of a short"); + device.shortBy = 0; + device.calls.clear(); + stream.pos = 7; + Require(api.ReadUShort(&stream, &error) == 0 && error == InvalidStreamOperation && device.calls.empty(), "short past the end does not call the device"); + stream.size = 0; + stream.pos = 2; + Require(api.ReadChar(&stream, &error) == 3 && error == 0 && stream.pos == 3, "device char does not check the size"); +} + +void TestCloseAndFree() { + auto stream = DeviceStream({}, 0); + api.Close(nullptr); + api.Close(&stream); + Require(device.closes == 0, "no close callback"); + stream.close = DeviceClose; + api.Close(&stream); + Require(device.closes == 1, "close callback"); + api.Free(nullptr, 0); + stream.memory = &memory; + api.Free(&stream, 1); + Require(device.closes == 2 && device.releases.empty(), "external stream is closed, not released"); + auto* owned = static_cast(std::malloc(sizeof(GuestStreamRec))); + *owned = stream; + owned->memory = &memory; + device.order.clear(); + api.Free(owned, 0); + Require(device.releases.size() == 1 && device.releases[0] == owned, "owned stream is released with the memory read before closing"); + Require(device.order.size() == 2 && device.order[0] == 'c' && device.order[1] == 'f', "close runs before release"); + stream.memory = nullptr; + const auto closes = device.closes; + Require(Throws([&] { api.Free(&stream, 0); }), "release without memory is unsupported"); + Require(device.closes == closes, "release without memory does not close the stream"); +} + +void TestReadFields() { + std::array data{{0x7F, 0xFF, 0xFE, 0x80, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0xFF, 0x80, 0x00, + 0xAA, 'A', 'B', 'C', 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70}}; + auto stream = MemoryStream(data.data(), data.size()); + const std::array fields{{ + {4, 0, 17}, {8, 1, 0}, {9, 8, 8}, {12, 2, 16}, {15, 2, 18}, {17, 4, 20}, {21, 8, 24}, {25, 1, 0}, {24, 3, 32}, {13, 4, 36}, {0, 0, 0}}}; + std::array target{}; + target.fill(0xEE); + Require(api.ReadFields(&stream, fields.data(), target.data()) == 0, "read fields"); + const auto load = [&](unsigned offset, unsigned size) { + std::uint64_t value = 0; + std::memcpy(&value, target.data() + offset, size); + return value; + }; + Require(load(0, 1) == 0x7F, "unsigned byte field"); + Require(load(8, 8) == Max, "signed byte field widened to eight bytes"); + Require(load(16, 2) == 0xFE80, "unsigned big-endian short field"); + Require(load(18, 2) == 0x8001, "signed little-endian short field"); + Require(load(20, 4) == 0x00000180u, "signed big-endian long field"); + Require(load(24, 8) == 0xFFFFFFFFFFFF8000u, "signed offset field widened to eight bytes"); + Require(target[32] == 'A' && target[33] == 'B' && target[34] == 'C' && target[35] == 0xEE, "byte sequence field"); + Require(load(36, 4) == 0x00001020u, "signed short field read past the frame"); + Require(!stream.cursor && !stream.limit && stream.pos == 17, "fields close their frame"); + const std::array tooLong{{{4, 0, 4}, {24, 5, 0}, {0, 0, 0}}}; + stream.pos = 0; + Require(api.ReadFields(&stream, tooLong.data(), target.data()) == InvalidStreamOperation && !stream.cursor && stream.pos == 4, "byte sequence past the frame"); + const std::array stop{{{8, 1, 40}, {7, 0, 0}}}; + stream.cursor = data.data() + 3; + stream.limit = data.data() + 4; + Require(api.ReadFields(&stream, stop.data(), target.data()) == 0 && target[40] == 0x80 && stream.cursor == data.data() + 4, "unknown operation ends the list and keeps the cursor"); + stream.pos = 24; + stream.cursor = nullptr; + const std::array late{{{4, 0, 1}, {0, 0, 0}}}; + Require(api.ReadFields(&stream, late.data(), target.data()) == InvalidStreamOperation && stream.pos == 24, "frame start past the end"); + Require(api.ReadFields(nullptr, nullptr, nullptr) == InvalidArgument, "null fields"); + Require(Throws([&] { api.ReadFields(nullptr, fields.data(), target.data()); }), "null stream with fields is unsupported"); + const std::array noStructure{{{8, 1, 0}, {0, 0, 0}}}; + stream.cursor = data.data(); + stream.limit = data.data() + 1; + Require(Throws([&] { api.ReadFields(&stream, noStructure.data(), nullptr); }), "null structure is unsupported"); + auto deviceStream = DeviceStream({0x12, 0x34, 0x56}, 3); + const std::array deviceFields{{{4, 0, 3}, {20, 4, 0}, {0, 0, 0}}}; + Require(api.ReadFields(&deviceStream, deviceFields.data(), target.data()) == 0 && load(0, 4) == 0x123456, "device fields"); + Require(device.allocations.size() == 1 && device.releases.size() == 1 && !deviceStream.base && !deviceStream.cursor, "device fields release their frame"); + deviceStream = DeviceStream({0x12, 0x34, 0x56}, 3); + const std::array deviceByte{{{4, 0, 3}, {8, 1, 0}, {0, 0, 0}}}; + Require(Throws([&] { api.ReadFields(&deviceStream, deviceByte.data(), nullptr); }), "null structure in a device frame is unsupported"); + Require(device.allocations.size() == 1 && device.releases.size() == 1, "refused fields release their device frame"); + Require(!deviceStream.base && !deviceStream.cursor && !deviceStream.limit && deviceStream.pos == 3, "refused fields leave no frame"); + stream.pos = 0; + const std::array memoryBytes{{{4, 0, 4}, {24, 2, 0}, {0, 0, 0}}}; + Require(Throws([&] { api.ReadFields(&stream, memoryBytes.data(), nullptr); }), "null structure in a memory frame is unsupported"); + Require(!stream.cursor && !stream.limit && stream.base == data.data() && stream.pos == 4, "refused fields leave no memory frame"); +} + +void TestNullStream() { + int error = 0; + std::uint8_t* frame = nullptr; + std::uint8_t byte = 0; + Require(Throws([] { api.OpenMemory(nullptr, nullptr, 0); }), "open memory"); + Require(Throws([] { api.Seek(nullptr, 0); }) && Throws([] { api.Skip(nullptr, 0); }) && Throws([] { api.Pos(nullptr); }), "seek, skip and position"); + Require(Throws([&] { api.Read(nullptr, &byte, 1); }) && Throws([&] { api.ReadAt(nullptr, 0, &byte, 1); }) && Throws([&] { api.TryRead(nullptr, &byte, 1); }), "reads"); + Require(Throws([] { api.EnterFrame(nullptr, 0); }) && Throws([] { api.ExitFrame(nullptr); }) && Throws([&] { api.ExtractFrame(nullptr, 0, &frame); }), "frames"); + Require(Throws([] { api.GetChar(nullptr); }) && Throws([] { api.GetULong(nullptr); }) && Throws([] { api.GetOffset(nullptr); }), "getters"); + Require(Throws([&] { api.ReadChar(nullptr, &error); }) && Throws([&] { api.ReadLong(nullptr, &error); }), "readers"); +} + +void Run() { + TestOpenMemoryAndPosition(); + TestMemoryReads(); + TestFrameGetters(); + TestMemoryReaders(); + TestDeviceStream(); + TestCloseAndFree(); + TestReadFields(); + TestNullStream(); +} + +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +#endif + Require(argc == 3, "expected library and export mode"); + const bool patched = std::strcmp(argv[2], "patched") == 0; +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); + const auto symbol = [&](const char* name) { return reinterpret_cast(GetProcAddress(library, name)); }; +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (!library) std::fprintf(stderr, "%s\n", dlerror()); + const auto symbol = [&](const char* name) { return dlsym(library, name); }; +#endif + Require(library != nullptr, "load requested library"); +#define RESOLVE(name, nid) \ + api.name = reinterpret_cast(symbol(patched ? nid : "FT_Stream_" #name "_nid_postfix")); \ + if (!api.name) std::fprintf(stderr, "Missing FT_Stream_%s\n", #name); \ + Require(api.name != nullptr, "missing stream export"); + STREAM_EXPORTS(RESOLVE) +#undef RESOLVE + Run(); + std::puts("FreeType stream cases passed"); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif +} diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 571579d6c1..7013351fc5 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -6,6 +6,8 @@ - [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned. +- [FT_Stream calls](../../../core/libs/prx/libSceFreeTypeOt/Stream.cpp) (libSceFreeTypeOt) - console signatures and FreeType version are unverified. The [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c) has both the signed readers FreeType removed in 2.4.5 and their unsigned replacements; the 36 calls follow 2.4.12 [ftstream.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftstream.c) and [ftobjs.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftobjs.c#L205-L219), the signed readers 2.4.4, on the 80-byte LP64 record with guest callbacks. A null pointer FreeType would dereference throws before any allocation, read or frame is left behind, as does `FT_Stream_ReadFields` with a null stream, whose error changed in 2.5.4. `FT_Stream_New` and `FT_Stream_Open` still throw. + - The import-resolution helpers in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) retain the PS5 3.20 NIDs from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs), including identified names and still-unidentified exports. Their provider-specific 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. - [ft_corner_is_flat, ft_corner_orientation](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) (libSceFreeTypeOt) - console prototypes and FreeType version equivalence are unverified. The LP64 signatures and 64-bit coordinate calculations follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftcalc.c): orientation returns +1 for counter-clockwise turns, -1 for clockwise turns and 0 for collinear inputs using 64-bit coordinate cross-products; flatness compares hypotenuse approximations via the alpha-max-plus-beta-min metric and returns 1 when the corner angle deviation does not exceed the threshold, 0 otherwise.