From 540bf7f47289a67f2dd8c4afdef05fb624843fa3 Mon Sep 17 00:00:00 2001 From: noamroger Date: Mon, 2 Nov 2026 11:08:51 +0000 Subject: [PATCH 1/2] feat(libSceFreeTypeOt): read Mac resource fork headers and data offsets Implement FT_Raccess_Get_HeaderInfo and FT_Raccess_Get_DataOffsets from FreeType 2.4.12 ftrfork.c on the Ot streams and ft_mem_*. FT_Raccess_Guess keeps throwing. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QgTGnM9fbEwqgSyhMfV4Jj --- core/libs/prx/libSceFreeTypeOt/CMakeLists.txt | 17 ++ core/libs/prx/libSceFreeTypeOt/Export.cpp | 10 - .../prx/libSceFreeTypeOt/include/Raccess.hpp | 22 +++ .../prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp | 1 + .../libs/prx/libSceFreeTypeOt/src/Raccess.cpp | 136 +++++++++++++ core/libs/tests/GuestFreeTypeOtRaccess.cpp | 185 ++++++++++++++++++ docs/dev/debt/libSceFreeTypeOt.md | 2 + 7 files changed, 363 insertions(+), 10 deletions(-) create mode 100644 core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp create mode 100644 core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp create mode 100644 core/libs/tests/GuestFreeTypeOtRaccess.cpp diff --git a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt index 7df600d7e0..6b94a30eec 100644 --- a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt +++ b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt @@ -60,3 +60,20 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceFreeTypeOt PRIVATE src/Raccess.cpp) +if(BUILD_TESTING) + add_test_executable(guest_freetype_ot_raccess_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOtRaccess.cpp) + target_include_directories(guest_freetype_ot_raccess_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_ot_raccess_tests PRIVATE libSceFreeTypeOt) + if(WIN32) + target_link_libraries(guest_freetype_ot_raccess_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_ot_raccess_tests) + add_test(NAME guest_freetype_ot_raccess COMMAND guest_freetype_ot_raccess_tests) + set_tests_properties(guest_freetype_ot_raccess PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_ot_raccess PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index fcbf9746ed..f34b074db6 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -289,16 +289,6 @@ int APS5_VABI sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch() { } APS5_EXPORT("KVbmKCGGowg", sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("hF3stKUWbWg", sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch); - -int APS5_VABI sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("p5GNJaXO2yE", sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch); - int APS5_VABI sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp b/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp new file mode 100644 index 0000000000..a4ea2eb42b --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "prx/libSceFreeTypeOt/include/Stream.hpp" + +#include + +namespace FreeType { + +struct GuestRaccessLibrary { + GuestMemoryRec* memory; +}; + +} + +extern "C" { + +int APS5_VABI FT_Raccess_Get_HeaderInfo_nid_postfix(FreeType::GuestRaccessLibrary* library, FreeType::GuestStreamRec* stream, std::int64_t rforkOffset, + std::int64_t* mapOffset, std::int64_t* rdataPos); +int APS5_VABI FT_Raccess_Get_DataOffsets_nid_postfix(FreeType::GuestRaccessLibrary* library, FreeType::GuestStreamRec* stream, std::int64_t mapOffset, + std::int64_t rdataPos, std::int64_t tag, std::int64_t** offsets, std::int64_t* count); + +} diff --git a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp index 1155682fa1..8518083a79 100644 --- a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp +++ b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp @@ -4,3 +4,4 @@ #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeTypeOt/include/Calc.hpp" #include "prx/libSceFreeTypeOt/include/Memory.hpp" +#include "prx/libSceFreeTypeOt/include/Raccess.hpp" diff --git a/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp b/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp new file mode 100644 index 0000000000..de12956c92 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp @@ -0,0 +1,136 @@ +#include "prx/libSceFreeTypeOt/include/Raccess.hpp" +#include "prx/libSceFreeTypeOt/include/Memory.hpp" +#include "prx/libc/include/General.hpp" + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +constexpr int CannotOpenResource = 0x01; +constexpr int UnknownFileFormat = 0x02; +constexpr std::int64_t RefSize = 16; +constexpr std::int64_t LongMax = std::numeric_limits::max(); +constexpr std::int64_t LongMin = std::numeric_limits::min(); + +struct GuestRForkRef { + std::uint16_t resId; + std::uint64_t offset; +}; + +static_assert(sizeof(GuestRForkRef) == RefSize && offsetof(GuestRForkRef, offset) == 8); + +std::int64_t Add(std::int64_t a, std::int64_t b, const char* function) { + if ((b > 0 && a > LongMax - b) || (b < 0 && a < LongMin - b)) NotImplemented_nid_no_patch(function); + return a + b; +} + +std::int64_t Field(const std::uint8_t* head, unsigned index, const char* function) { + if (head[index] >= 0x80) NotImplemented_nid_no_patch(function); + return static_cast(head[index]) << 24 | head[index + 1] << 16 | head[index + 2] << 8 | head[index + 3]; +} + +} + +extern "C" { + +int APS5_VABI FT_Raccess_Get_HeaderInfo_nid_postfix(GuestRaccessLibrary*, GuestStreamRec* stream, std::int64_t rforkOffset, + std::int64_t* mapOffset, std::int64_t* rdataPos) { + const char* function = __func__; + std::uint8_t head[16]; + std::uint8_t head2[16]; + int error = FT_Stream_Seek_nid_postfix(stream, static_cast(rforkOffset)); + if (error) return error; + error = FT_Stream_Read_nid_postfix(stream, head, 16); + if (error) return error; + const std::int64_t dataStart = Add(rforkOffset, Field(head, 0, function), function); + const std::int64_t mapPos = Add(rforkOffset, Field(head, 4, function), function); + const std::int64_t dataLength = Field(head, 8, function); + if (!rdataPos) NotImplemented_nid_no_patch(function); + *rdataPos = dataStart; + if (Add(dataStart, dataLength, function) != mapPos || mapPos == rforkOffset) return UnknownFileFormat; + error = FT_Stream_Seek_nid_postfix(stream, static_cast(mapPos)); + if (error) return error; + head2[15] = static_cast(head[15] + 1); + error = FT_Stream_Read_nid_postfix(stream, head2, 16); + if (error) return error; + bool allZeros = true; + bool allMatch = true; + for (unsigned index = 0; index < 16; ++index) { + if (head2[index] != 0) allZeros = false; + if (head2[index] != head[index]) allMatch = false; + } + if (!allZeros && !allMatch) return UnknownFileFormat; + FT_Stream_Skip_nid_postfix(stream, 8); + const std::int64_t typeList = FT_Stream_ReadUShort_nid_postfix(stream, &error); + if (error) return error; + error = FT_Stream_Seek_nid_postfix(stream, static_cast(Add(mapPos, typeList, function))); + if (error) return error; + if (!mapOffset) NotImplemented_nid_no_patch(function); + *mapOffset = mapPos + typeList; + return 0; +} + +int APS5_VABI FT_Raccess_Get_DataOffsets_nid_postfix(GuestRaccessLibrary* library, GuestStreamRec* stream, std::int64_t mapOffset, + std::int64_t rdataPos, std::int64_t tag, std::int64_t** offsets, std::int64_t* count) { + const char* function = __func__; + if (!library) NotImplemented_nid_no_patch(function); + GuestMemoryRec* memory = library->memory; + int error = FT_Stream_Seek_nid_postfix(stream, static_cast(mapOffset)); + if (error) return error; + const int types = FT_Stream_ReadUShort_nid_postfix(stream, &error) + 1; + if (error) return error; + for (int type = 0; type < types; ++type) { + const std::int64_t typeTag = FT_Stream_ReadLong_nid_postfix(stream, &error); + if (error) return error; + const int subCount = FT_Stream_ReadUShort_nid_postfix(stream, &error); + if (error) return error; + const std::int64_t refList = FT_Stream_ReadUShort_nid_postfix(stream, &error); + if (error) return error; + if (typeTag != tag) continue; + if (!count) NotImplemented_nid_no_patch(function); + const std::int64_t refs = subCount + 1; + *count = refs; + error = FT_Stream_Seek_nid_postfix(stream, static_cast(Add(refList, mapOffset, function))); + if (error) return error; + if (!memory) NotImplemented_nid_no_patch(function); + auto* ref = static_cast(ft_mem_realloc_nid_postfix(memory, RefSize, 0, refs, nullptr, &error)); + if (error) return error; + for (std::int64_t index = 0; index < refs; ++index) { + ref[index].resId = FT_Stream_ReadUShort_nid_postfix(stream, &error); + if (!error) error = FT_Stream_Skip_nid_postfix(stream, 2); + std::int64_t value = 0; + if (!error) value = FT_Stream_ReadLong_nid_postfix(stream, &error); + if (!error) error = FT_Stream_Skip_nid_postfix(stream, 4); + if (error) { + ft_mem_free_nid_postfix(memory, ref); + return error; + } + ref[index].offset = static_cast(value) & 0xFFFFFF; + } + std::stable_sort(ref, ref + refs, [](const GuestRForkRef& a, const GuestRForkRef& b) { return a.resId < b.resId; }); + for (std::int64_t index = 1; index < refs; ++index) { + if (ref[index].resId == ref[index - 1].resId && ref[index].offset != ref[index - 1].offset) { + ft_mem_free_nid_postfix(memory, ref); + NotImplemented_nid_no_patch(function); + } + } + auto* result = static_cast(ft_mem_realloc_nid_postfix(memory, sizeof(std::int64_t), 0, refs, nullptr, &error)); + if (!error) { + for (std::int64_t index = 0; index < refs; ++index) { + result[index] = Add(rdataPos, static_cast(ref[index].offset), function); + } + if (!offsets) NotImplemented_nid_no_patch(function); + *offsets = result; + } + ft_mem_free_nid_postfix(memory, ref); + return error; + } + return CannotOpenResource; +} + +} diff --git a/core/libs/tests/GuestFreeTypeOtRaccess.cpp b/core/libs/tests/GuestFreeTypeOtRaccess.cpp new file mode 100644 index 0000000000..d75bf31c1e --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOtRaccess.cpp @@ -0,0 +1,185 @@ +#include "prx/libSceFreeTypeOt/include/Memory.hpp" +#include "prx/libSceFreeTypeOt/include/Raccess.hpp" +#include +#include +#include +#include +#include + +namespace { + +using FreeType::GuestMemoryRec; +using FreeType::GuestRaccessLibrary; +using FreeType::GuestStreamRec; + +constexpr std::int64_t Post = 0x504F5354; +constexpr std::int64_t Sfnt = 0x73666E74; + +struct Recorder { + GuestMemoryRec memory{}; + int allocs = 0; + int frees = 0; +}; + +void* APS5_VABI RecordAlloc(GuestMemoryRec* memory, std::int64_t size) { + ++static_cast(memory->user)->allocs; + return std::malloc(static_cast(size)); +} + +void APS5_VABI RecordFree(GuestMemoryRec* memory, void* block) { + ++static_cast(memory->user)->frees; + std::free(block); +} + +void* APS5_VABI RecordRealloc(GuestMemoryRec*, std::int64_t, std::int64_t size, void* block) { + return std::realloc(block, static_cast(size)); +} + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "FreeType Ot raccess: %s\n", message); + std::exit(1); +} + +void Put16(std::vector& bytes, std::size_t at, std::uint32_t value) { + bytes[at] = static_cast(value >> 8); + bytes[at + 1] = static_cast(value); +} + +void Put32(std::vector& bytes, std::size_t at, std::uint32_t value) { + Put16(bytes, at, value >> 16); + Put16(bytes, at + 2, value); +} + +struct Fork { + std::vector bytes; + std::size_t prefix; + std::uint32_t dataStart = 0x100; + std::uint32_t dataLength = 0x40; + std::uint32_t mapPos = 0x140; + std::uint32_t typeList = 28; +}; + +Fork MakeFork(std::size_t prefix, bool zeroCopy) { + Fork fork; + fork.prefix = prefix; + fork.bytes.assign(prefix + 0x140 + 0x70, 0xEE); + const std::size_t base = prefix; + Put32(fork.bytes, base, fork.dataStart); + Put32(fork.bytes, base + 4, fork.mapPos); + Put32(fork.bytes, base + 8, fork.dataLength); + Put32(fork.bytes, base + 12, 0x60); + const std::size_t map = base + fork.mapPos; + for (std::size_t index = 0; index < 16; ++index) fork.bytes[map + index] = zeroCopy ? 0 : fork.bytes[base + index]; + Put16(fork.bytes, map + 24, fork.typeList); + const std::size_t types = map + fork.typeList; + Put16(fork.bytes, types, 1); + Put32(fork.bytes, types + 2, Sfnt); + Put16(fork.bytes, types + 6, 0); + Put16(fork.bytes, types + 8, 22); + Put32(fork.bytes, types + 10, Post); + Put16(fork.bytes, types + 14, 2); + Put16(fork.bytes, types + 16, 34); + const std::uint32_t refs[4][2] = {{0x0080, 0x00000010}, {0x0102, 0xA0000020}, {0x0100, 0x00000030}, {0x0101, 0x7F000008}}; + for (std::size_t index = 0; index < 4; ++index) { + const std::size_t ref = types + 22 + index * 12; + Put16(fork.bytes, ref, refs[index][0]); + Put16(fork.bytes, ref + 2, 0xFFFF); + Put32(fork.bytes, ref + 4, refs[index][1]); + Put32(fork.bytes, ref + 8, 0); + } + return fork; +} + +struct Session { + Recorder recorder; + GuestRaccessLibrary library{}; + GuestStreamRec stream{}; + + explicit Session(const std::vector& bytes) { + recorder.memory = {&recorder, RecordAlloc, RecordFree, RecordRealloc}; + library.memory = &recorder.memory; + FT_Stream_OpenMemory_nid_postfix(&stream, bytes.data(), bytes.size()); + } +}; + +void HeaderAndOffsets(std::size_t prefix, bool zeroCopy) { + const Fork fork = MakeFork(prefix, zeroCopy); + Session session(fork.bytes); + std::int64_t mapOffset = -1; + std::int64_t rdataPos = -1; + const auto offset = static_cast(prefix); + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(&session.library, &session.stream, offset, &mapOffset, &rdataPos) == 0, "a resource fork header is accepted"); + Require(rdataPos == offset + 0x100 && mapOffset == offset + 0x140 + 28, "header info adds the fork offset to the data and type list positions"); + Require(session.stream.pos == static_cast(mapOffset), "header info leaves the stream at the type list"); + + std::int64_t* offsets = nullptr; + std::int64_t count = 0; + Require(FT_Raccess_Get_DataOffsets_nid_postfix(&session.library, &session.stream, mapOffset, rdataPos, Post, &offsets, &count) == 0, "a present tag is found"); + Require(count == 3 && offsets, "the subcount plus one references are returned"); + Require(offsets[0] == rdataPos + 0x30 && offsets[1] == rdataPos + 0x08 && offsets[2] == rdataPos + 0x20, "offsets are sorted by resource ID and drop the attribute byte"); + Require(session.recorder.allocs == 2 && session.recorder.frees == 1, "the reference array is freed and the offsets array is kept"); + ft_mem_free_nid_postfix(&session.recorder.memory, offsets); + + offsets = nullptr; + count = 0; + Require(FT_Raccess_Get_DataOffsets_nid_postfix(&session.library, &session.stream, mapOffset, rdataPos, Sfnt, &offsets, &count) == 0, "the first type is found"); + Require(count == 1 && offsets[0] == rdataPos + 0x10, "a single reference is returned"); + ft_mem_free_nid_postfix(&session.recorder.memory, offsets); + + offsets = nullptr; + count = -1; + const int allocs = session.recorder.allocs; + Require(FT_Raccess_Get_DataOffsets_nid_postfix(&session.library, &session.stream, mapOffset, rdataPos, 0x4E464E54, &offsets, &count) == 0x01, "a missing tag is Cannot_Open_Resource"); + Require(count == -1 && !offsets && session.recorder.allocs == allocs, "a missing tag writes and allocates nothing"); +} + +void RejectedHeaders() { + Fork fork = MakeFork(0, false); + Put32(fork.bytes, 8, 0x41); + Session lengths(fork.bytes); + std::int64_t mapOffset = -1; + std::int64_t rdataPos = -1; + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(&lengths.library, &lengths.stream, 0, &mapOffset, &rdataPos) == 0x02, "data that does not end at the map is Unknown_File_Format"); + Require(rdataPos == 0x100 && mapOffset == -1, "the data position is written before the length check"); + + fork = MakeFork(0, false); + fork.bytes[0x140 + 3] ^= 1; + Session copy(fork.bytes); + rdataPos = -1; + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(©.library, ©.stream, 0, &mapOffset, &rdataPos) == 0x02, "a map header that neither matches nor is zero is Unknown_File_Format"); + + fork = MakeFork(0, false); + Put32(fork.bytes, 0, 0); + Put32(fork.bytes, 4, 0); + Put32(fork.bytes, 8, 0); + Session empty(fork.bytes); + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(&empty.library, &empty.stream, 0, &mapOffset, &rdataPos) == 0x02, "a map at the fork offset is Unknown_File_Format"); + + const std::vector shortStream(10, 0); + Session truncated(shortStream); + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(&truncated.library, &truncated.stream, 0, &mapOffset, &rdataPos) == 0x55, "a short header is Invalid_Stream_Operation"); + Require(FT_Raccess_Get_HeaderInfo_nid_postfix(&truncated.library, &truncated.stream, 11, &mapOffset, &rdataPos) == 0x55, "a fork offset past the stream is Invalid_Stream_Operation"); +} + +void TruncatedReferences() { + Fork fork = MakeFork(0, false); + fork.bytes.resize(0x140 + 28 + 22 + 12 + 6); + Session session(fork.bytes); + std::int64_t* offsets = nullptr; + std::int64_t count = 0; + Require(FT_Raccess_Get_DataOffsets_nid_postfix(&session.library, &session.stream, 0x140 + 28, 0x100, Post, &offsets, &count) == 0x55, "a cut reference list is Invalid_Stream_Operation"); + Require(count == 3 && !offsets, "the count is written before the references are read"); + Require(session.recorder.allocs == 1 && session.recorder.frees == 1, "the reference array is freed on a read error"); +} + +} + +int main() { + HeaderAndOffsets(0, false); + HeaderAndOffsets(0x80, true); + RejectedHeaders(); + TruncatedReferences(); + std::puts("Guest FreeType Ot raccess tests passed"); + return 0; +} diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 25fd2dfd7d..42fb43068e 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -19,3 +19,5 @@ - [ft_mem_alloc, ft_mem_qalloc, ft_mem_realloc, ft_mem_qrealloc, ft_mem_free, ft_mem_dup, ft_mem_strdup, ft_mem_strcpyn](../../../core/libs/prx/libSceFreeTypeOt/src/Memory.cpp) (libSceFreeTypeOt) - the exports carry the NIDs of these FreeType names; console prototypes and FreeType version equivalence are unverified. The LP64 signatures, the guest `FT_MemoryRec` callbacks and the error codes (Invalid_Argument 0x06, Array_Too_Large 0x0A, Out_Of_Memory 0x40) follow FreeType 2.4.12 [ftutil.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftutil.c#L49-L221). Bundled FreeType 2.13.3 differs only in `ft_mem_qrealloc` from a count of 0, which it does not zero. As in a release build, `ft_mem_qrealloc` does not check the `FT_ASSERT` that a block with no current items is null. The current byte size passed to `realloc` wraps where the source's signed product would overflow. - [FT_Sin, FT_Tan, FT_Vector_Unit, FT_Outline_Get_Orientation, FT_Outline_Copy, FT_Outline_Decompose](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOt) - built from the 2.13.3-based libSceFreeType sources; the provider is dated to 2.4.11-2.4.12 elsewhere and its version is unmeasured. Against [2.4.12](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftoutln.c): `FT_Outline_Copy` returns `Invalid_Outline`, not `Invalid_Argument`, for a null outline; `FT_Outline_Get_Orientation` returns `FT_ORIENTATION_NONE` for a zero-width or zero-height box and beyond ±0x1000000, and shifts coordinates rather than differences; counts of 0x8000 or more are not negative `short`s. 2.4.11 does not shift the area and uses other CORDIC constants than the 2.4.12 ones used here. FT_Cos and FT_Angle_Diff are also exported but absent from the 3.20 inventory. + +- [FT_Raccess_Get_HeaderInfo, FT_Raccess_Get_DataOffsets](../../../core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp) (libSceFreeTypeOt) - console prototypes and FreeType version equivalence are unverified. Both follow 2.4.12 [ftrfork.c](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftrfork.c#L49-L237) and read only `memory` from the library record. `FT_Raccess_Get_DataOffsets` takes the seven arguments of 2.4.11-2.5.2; from 2.5.3 `FT_Bool sort_by_res_id` precedes `offsets`. Header fields with the top bit set and overflowing `FT_Long` sums, undefined in that source, throw; 2.13.3 adds overlap, stream size and count checks that 2.4.12 lacks. Equal resource IDs with different offsets throw, since `ft_qsort` leaves their order unspecified. Null pointers throw where 2.4.12 dereferences them. `FT_Raccess_Guess` still throws. From 500490fc9de4d3bddae86dec7f40e48d333be2f7 Mon Sep 17 00:00:00 2001 From: noamroger Date: Mon, 2 Nov 2026 15:01:06 +0000 Subject: [PATCH 2/2] fix(libSceFreeTypeOt): credit FreeType in the resource fork port Add the FreeType provenance, copyright and licence header to the files derived from ftrfork.c and ftrfork.h, and update the count of unknown Ot exports. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QgTGnM9fbEwqgSyhMfV4Jj --- core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp | 6 ++++++ core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp | 6 ++++++ docs/dev/debt/libSceFreeTypeOt.md | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp b/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp index a4ea2eb42b..7e1f427923 100644 --- a/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp +++ b/core/libs/prx/libSceFreeTypeOt/include/Raccess.hpp @@ -1,3 +1,9 @@ +// Derived from FreeType include/freetype/internal/ftrfork.h (FreeType 2.4.12, commit cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9). +// Copyright 2004, 2006, 2007, 2012 by Masatake YAMATO and Redhat K.K. +// This file is part of the FreeType project, and may only be used, modified, and distributed +// under the terms of the FreeType project license, LICENSE.TXT. By continuing to use, modify, +// or distribute this file you indicate that you have read the license and understand and accept it fully. + #pragma once #include "prx/libSceFreeTypeOt/include/Stream.hpp" diff --git a/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp b/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp index de12956c92..e0b7ed08af 100644 --- a/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp +++ b/core/libs/prx/libSceFreeTypeOt/src/Raccess.cpp @@ -1,3 +1,9 @@ +// Derived from FreeType src/base/ftrfork.c (FreeType 2.4.12, commit cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9). +// Copyright 2004-2010, 2013 by Masatake YAMATO and Redhat K.K. +// This file is part of the FreeType project, and may only be used, modified, and distributed +// under the terms of the FreeType project license, LICENSE.TXT. By continuing to use, modify, +// or distribute this file you indicate that you have read the license and understand and accept it fully. + #include "prx/libSceFreeTypeOt/include/Raccess.hpp" #include "prx/libSceFreeTypeOt/include/Memory.hpp" #include "prx/libc/include/General.hpp" diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 42fb43068e..a1f61aa820 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -1,7 +1,7 @@ # libSceFreeTypeOt technical debt - The FT_Glyph_Copy, FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy, FT_Outline_Decompose, FT_Stroker_New, FT_Stroker_Done, FT_Stroker_GetCounts, FT_Stroker_BeginSubPath, FT_Stroker_LineTo, FT_Stroker_EndSubPath, FT_Stroker_Set, FT_Stroker_Rewind, FT_Stroker_GetBorderCounts, FT_Stroker_ConicTo and FT_Stroker_CubicTo NIDs in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider. -- The 68 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/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 66 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/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. ### Unknown function info - [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.