From 9f311ca8c14d0b1b9cd49547253376975fcfcdd1 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Tue, 20 Oct 2026 12:21:00 +0530 Subject: [PATCH 1/2] feat(libSceFreeType): support memory fonts and face cleanup --- core/libs/prx/libSceFreeType/Export.cpp | 72 ++++++++- .../libSceFreeType/include/GuestFreeType.hpp | 5 +- .../libSceFreeType/include/GuestObjects.hpp | 5 + .../prx/libSceFreeType/src/GuestObjects.cpp | 4 +- core/libs/tests/GuestFreeType.cpp | 144 +++++++++++++++++- docs/dev/debt/libSceFreeType.md | 4 +- 6 files changed, 221 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index 0a098e6024..45eaafe81e 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -15,6 +15,40 @@ using namespace FreeType; namespace { +void FinalizeSlot(void* object) { + auto* host = static_cast(object); + auto* face = static_cast(host->generic.data); + face->rec.glyph = nullptr; + if (face->slot.generic.finalizer) face->slot.generic.finalizer(&face->slot); +} + +void FinalizeSize(void* object) { + auto* host = static_cast(object); + auto* face = static_cast(host->generic.data); + if (face->size.generic.finalizer) face->size.generic.finalizer(&face->size); + face->rec.size = nullptr; +} + +void FinalizeFace(void* object) { + auto* host = static_cast(object); + auto* face = static_cast(host->generic.data); + if (face->rec.generic.finalizer) face->rec.generic.finalizer(&face->rec); +} + +void PublishFace(GuestLibrary* library, std::unique_ptr face, FT_Face host, GuestFaceRec** aface) { + std::unique_ptr hostOwner(host, &FT_Done_Face); + face->library = library; + face->host = host; + SyncFace(*face); + auto* owner = face.get(); + library->faces.push_back(std::move(face)); + host->generic = {owner, FinalizeFace}; + if (host->glyph) host->glyph->generic = {owner, FinalizeSlot}; + if (host->size) host->size->generic = {owner, FinalizeSize}; + *aface = &owner->rec; + hostOwner.release(); +} + bool ReadFontFile(const char* path, std::vector& bytes) { std::filesystem::path hostPath; if (ResolvePathError_nid_no_patch(path, hostPath) != 0) return false; @@ -58,13 +92,19 @@ extern "C" { int APS5_VABI FT_Init_FreeType_nid_postfix(GuestLibrary** alibrary) { if (!alibrary) return FT_Err_Invalid_Argument; - FT_Library host = nullptr; - const FT_Error error = FT_Init_FreeType(&host); + auto library = std::make_unique(); + const FT_Error error = FT_Init_FreeType(&library->host); if (error != 0) return error; - *alibrary = new GuestLibrary{host}; + *alibrary = library.release(); return 0; } +int APS5_VABI FT_Done_FreeType_nid_postfix(GuestLibrary* library) { + const FT_Error error = FT_Done_FreeType(library ? library->host : nullptr); + if (error == 0) delete library; + return error; +} + int APS5_VABI FT_New_Face_nid_postfix(GuestLibrary* library, const char* filepathname, std::int64_t face_index, GuestFaceRec** aface) { if (!filepathname) return FT_Err_Invalid_Argument; const std::int64_t faceIndex = OpenFaceIndex(face_index); @@ -77,13 +117,31 @@ int APS5_VABI FT_New_Face_nid_postfix(GuestLibrary* library, const char* filepat FT_Face host = nullptr; const FT_Error error = FT_New_Memory_Face(library->host, face->bytes.data(), static_cast(face->bytes.size()), static_cast(faceIndex), aface ? &host : nullptr); if (error != 0 || !aface) return error; - face->library = library; - face->host = host; - SyncFace(*face); - *aface = &face.release()->rec; + PublishFace(library, std::move(face), host, aface); return 0; } +int APS5_VABI FT_New_Memory_Face_nid_postfix(GuestLibrary* library, const unsigned char* fileBase, std::int64_t fileSize, std::int64_t faceIndex, GuestFaceRec** aface) { + if (!fileBase) return FT_Err_Invalid_Argument; + if (!library) return FT_Err_Invalid_Library_Handle; + if (fileSize < std::numeric_limits::min() || fileSize > std::numeric_limits::max()) { + throw std::runtime_error("FT_New_Memory_Face: a font buffer size outside the host FT_Long range is not implemented"); + } + auto face = std::make_unique(); + FT_Face host = nullptr; + const FT_Error error = FT_New_Memory_Face(library->host, fileBase, static_cast(fileSize), static_cast(OpenFaceIndex(faceIndex)), aface ? &host : nullptr); + if (error != 0 || !aface) return error; + PublishFace(library, std::move(face), host, aface); + return 0; +} + +int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec* face) { + GuestFace* owner = FaceOf(face); + const FT_Error error = FT_Done_Face(owner ? owner->host : nullptr); + if (error == 0) owner->library->faces.remove_if([owner](const auto& entry) { return entry.get() == owner; }); + return error; +} + int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t pixel_width, std::uint32_t pixel_height) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Set_Pixel_Sizes(owner ? owner->host : nullptr, pixel_width, pixel_height); diff --git a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp index 2cf157b746..1a2a074378 100644 --- a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp @@ -1,6 +1,8 @@ #ifndef CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTFREETYPE_HPP #define CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTFREETYPE_HPP +#include "prx/libc/include/general/VabiMacros.hpp" + #include #include @@ -8,7 +10,7 @@ namespace FreeType { struct GuestGeneric { void* data; - void* finalizer; + void (APS5_VABI *finalizer)(void*); }; struct GuestVector { @@ -147,6 +149,7 @@ struct GuestFaceRec { void* internal; }; +static_assert(sizeof(GuestGeneric) == 16 && offsetof(GuestGeneric, finalizer) == 8); static_assert(sizeof(GuestBitmapSize) == 32 && offsetof(GuestBitmapSize, size) == 8); static_assert(sizeof(GuestCharMapRec) == 16 && offsetof(GuestCharMapRec, platform_id) == 12); static_assert(sizeof(GuestSizeMetrics) == 56 && offsetof(GuestSizeMetrics, x_scale) == 8); diff --git a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp index 9abdae1f81..d237147b89 100644 --- a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp @@ -6,12 +6,17 @@ #include #include FT_FREETYPE_H +#include +#include #include namespace FreeType { +struct GuestFace; + struct GuestLibrary { FT_Library host = nullptr; + std::list> faces; }; struct GuestFace { diff --git a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp index 5319615c39..79662344d2 100644 --- a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp +++ b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp @@ -49,8 +49,8 @@ void SyncFace(GuestFace& face) { rec.max_advance_height = host->max_advance_height; rec.underline_position = host->underline_position; rec.underline_thickness = host->underline_thickness; - rec.glyph = &face.slot; - rec.size = &face.size; + rec.glyph = host->glyph ? &face.slot : nullptr; + rec.size = host->size ? &face.size : nullptr; rec.internal = &face; SyncSize(face); SyncSlot(face); diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index bfb51cb340..ea26c80654 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -23,7 +23,10 @@ using namespace FreeType; extern "C" { int APS5_VABI FT_Init_FreeType_nid_postfix(void**); +int APS5_VABI FT_Done_FreeType_nid_postfix(void*); int APS5_VABI FT_New_Face_nid_postfix(void*, const char*, std::int64_t, GuestFaceRec**); +int APS5_VABI FT_New_Memory_Face_nid_postfix(void*, const unsigned char*, std::int64_t, std::int64_t, GuestFaceRec**); +int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec*); int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec*, std::uint32_t, std::uint32_t); std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec*, std::uint64_t); int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec*, std::uint32_t, std::int32_t); @@ -84,6 +87,58 @@ static void SlotMatches(const GuestGlyphSlotRec* slot, FT_GlyphSlot host) { Require(slot->lsb_delta == host->lsb_delta && slot->rsb_delta == host->rsb_delta && slot->control_len == host->control_len); } +struct Finalizers { + std::vector calls; +}; + +static void NativeSlotFinalizer(void* object) { + auto* slot = static_cast(object); + Require(slot->face->glyph == nullptr); + static_cast(slot->generic.data)->calls.push_back(1); +} + +static void NativeSizeFinalizer(void* object) { + auto* size = static_cast(object); + Require(size->face->size == size); + static_cast(size->generic.data)->calls.push_back(2); +} + +static void NativeFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back(3); +} + +static void APS5_VABI GuestSlotFinalizer(void* object) { + auto* slot = static_cast(object); + Require(static_cast(slot->face)->glyph == nullptr); + static_cast(slot->generic.data)->calls.push_back(1); +} + +static void APS5_VABI GuestSizeFinalizer(void* object) { + auto* size = static_cast(object); + Require(static_cast(size->face)->size == size); + static_cast(size->generic.data)->calls.push_back(2); +} + +static void APS5_VABI GuestFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back(3); +} + +static void SetFinalizers(FT_Face face, Finalizers& events) { + face->generic = {&events, NativeFaceFinalizer}; + face->glyph->generic = {&events, NativeSlotFinalizer}; + face->size->generic = {&events, NativeSizeFinalizer}; +} + +static void SetFinalizers(GuestFaceRec* face, Finalizers& events) { + face->generic = {&events, GuestFaceFinalizer}; + face->glyph->generic = {&events, GuestSlotFinalizer}; + face->size->generic = {&events, GuestSizeFinalizer}; +} + int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_freetype-" + std::to_string(std::random_device{}())); std::filesystem::create_directories(root / "app0"); @@ -106,6 +161,8 @@ int main() { Require(FT_Init_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Argument); void* library = nullptr; Require(FT_Init_FreeType_nid_postfix(&library) == 0 && library != nullptr); + Require(FT_Done_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Library_Handle); + Require(FT_Done_Face_nid_postfix(nullptr) == FT_Err_Invalid_Face_Handle); GuestFaceRec* face = nullptr; Require(FT_New_Face_nid_postfix(library, nullptr, 0, &face) == FT_Err_Invalid_Argument); @@ -120,6 +177,54 @@ int main() { Require(FT_New_Face_nid_postfix(library, "/app0/font.ttf", 0, &face) == 0 && face != nullptr && face != masked); FaceMatches(face, host, library); + const auto fontSize = static_cast(font.size()); + GuestFaceRec* memoryFace = nullptr; + Require(FT_New_Memory_Face_nid_postfix(library, nullptr, fontSize, 0, &memoryFace) == FT_Err_Invalid_Argument); + Require(FT_New_Memory_Face_nid_postfix(nullptr, font.data(), fontSize, 0, &memoryFace) == FT_Err_Invalid_Library_Handle); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0, nullptr) == FT_Err_Invalid_Argument); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, nullptr) == 0); + FT_Face nativeProbe = nullptr; + GuestFaceRec* probe = nullptr; + Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast(font.size()), -1, &nativeProbe) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, &probe) == 0 && probe != nullptr); + Require(probe->num_faces == nativeProbe->num_faces && probe->face_index == nativeProbe->face_index); + Require((probe->glyph == nullptr) == (nativeProbe->glyph == nullptr) && (probe->size == nullptr) == (nativeProbe->size == nullptr)); + Finalizers nativeProbeEvents; + Finalizers guestProbeEvents; + nativeProbe->generic = {&nativeProbeEvents, NativeFaceFinalizer}; + probe->generic = {&guestProbeEvents, GuestFaceFinalizer}; + Require(FT_Done_Face(nativeProbe) == 0 && FT_Done_Face_nid_postfix(probe) == 0); + Require(nativeProbeEvents.calls == std::vector({3}) && guestProbeEvents.calls == nativeProbeEvents.calls); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 1, &memoryFace) == secondFace && memoryFace == nullptr); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0x80000001ll, &memoryFace) == secondFace && memoryFace == nullptr); + FT_Face invalidHost = nullptr; + for (const FT_Long length : {0L, 4L}) { + const FT_Error expected = FT_New_Memory_Face(hostLibrary, font.data(), length, 0, &invalidHost); + Require(expected != 0 && invalidHost == nullptr); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), length, 0, &memoryFace) == expected && memoryFace == nullptr); + } + const unsigned char invalidFont[32]{}; + const FT_Error invalidFormat = FT_New_Memory_Face(hostLibrary, invalidFont, sizeof(invalidFont), 0, &invalidHost); + Require(invalidFormat != 0 && invalidHost == nullptr); + Require(FT_New_Memory_Face_nid_postfix(library, invalidFont, sizeof(invalidFont), 0, &memoryFace) == invalidFormat && memoryFace == nullptr); + if constexpr (sizeof(FT_Long) < sizeof(std::int64_t)) { + for (const std::int64_t length : {0x80000000ll, -0x80000001ll}) { + Require(ThrowsWith([&] { FT_New_Memory_Face_nid_postfix(library, font.data(), length, 0, &memoryFace); }, + "FT_New_Memory_Face: a font buffer size outside the host FT_Long range is not implemented")); + Require(memoryFace == nullptr); + } + } + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0x180000000ll, &memoryFace) == 0 && memoryFace != nullptr); + FaceMatches(memoryFace, host, library); + Require(memoryFace != face && memoryFace->glyph != face->glyph); + Require(FT_Set_Pixel_Sizes_nid_postfix(memoryFace, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0); + Require(FT_Get_Char_Index_nid_postfix(memoryFace, 'A') == 1); + Require(FT_Load_Glyph_nid_postfix(memoryFace, 1, FT_LOAD_DEFAULT) == 0 && FT_Load_Glyph(host, 1, FT_LOAD_DEFAULT) == 0); + SlotMatches(memoryFace->glyph, host->glyph); + Require(FT_Render_Glyph_nid_postfix(memoryFace->glyph, FT_RENDER_MODE_NORMAL) == 0 && FT_Render_Glyph(host->glyph, FT_RENDER_MODE_NORMAL) == 0); + SlotMatches(memoryFace->glyph, host->glyph); + Require(face->size->metrics.y_ppem == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(nullptr, 0, 100) == FT_Err_Invalid_Face_Handle); Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0); const auto& metrics = host->size->metrics; @@ -154,8 +259,43 @@ int main() { slot->outline.points = original; Require(FT_Render_Glyph_nid_postfix(slot, FT_RENDER_MODE_NORMAL) == 0 && slot->bitmap_left == 10); - FT_Done_Face(host); - FT_Done_FreeType(hostLibrary); + Finalizers nativeDone; + Finalizers guestDone; + SetFinalizers(host, nativeDone); + SetFinalizers(face, guestDone); + Require(FT_Done_Face(host) == 0); + Require(FT_Done_Face_nid_postfix(face) == 0); + Require(nativeDone.calls == std::vector({1, 2, 3}) && guestDone.calls == nativeDone.calls); + Require(FT_Get_Char_Index_nid_postfix(masked, 'A') == 1); + + FT_Face nativeFirst = nullptr; + FT_Face nativeSecond = nullptr; + Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast(font.size()), 0, &nativeFirst) == 0); + Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast(font.size()), 0, &nativeSecond) == 0); + Finalizers nativeFirstEvents; + Finalizers nativeSecondEvents; + Finalizers guestFirstEvents; + Finalizers guestSecondEvents; + SetFinalizers(nativeFirst, nativeFirstEvents); + SetFinalizers(nativeSecond, nativeSecondEvents); + SetFinalizers(masked, guestFirstEvents); + SetFinalizers(memoryFace, guestSecondEvents); + Require(FT_Done_FreeType(hostLibrary) == 0); + + void* independent = nullptr; + GuestFaceRec* independentFace = nullptr; + Require(FT_Init_FreeType_nid_postfix(&independent) == 0); + Require(FT_New_Memory_Face_nid_postfix(independent, font.data(), fontSize, 0, &independentFace) == 0); + Finalizers independentEvents; + SetFinalizers(independentFace, independentEvents); + Require(FT_Done_FreeType_nid_postfix(library) == 0); + Require(guestFirstEvents.calls == nativeFirstEvents.calls && guestSecondEvents.calls == nativeSecondEvents.calls); + Require(guestFirstEvents.calls == nativeDone.calls && independentEvents.calls.empty()); + Require(FT_Get_Char_Index_nid_postfix(independentFace, 'A') == 1); + Require(FT_Done_FreeType_nid_postfix(independent) == 0 && independentEvents.calls == nativeDone.calls); + + void* empty = nullptr; + Require(FT_Init_FreeType_nid_postfix(&empty) == 0 && FT_Done_FreeType_nid_postfix(empty) == 0); std::filesystem::current_path(previous); std::error_code ignored; std::filesystem::remove_all(root, ignored); diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index fa594755cb..19872daed2 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -6,6 +6,8 @@ - [FT_Init_FreeType, FT_New_Face, FT_Set_Pixel_Sizes, FT_Get_Char_Index, FT_Load_Glyph, FT_Render_Glyph](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the PS4 library is taken to be FreeType 2 built for LP64: the face, size, charmap, glyph slot, bitmap and outline layouts follow the public headers of the bundled FreeType 2.13.3 with a 64-bit `long`, which matches the offsets the OpenOrbis samples read (`face->glyph` at 0x98, `slot->advance` at 0x80, `slot->bitmap` at 0x98, `slot->bitmap_left` at 0xc0). Which FreeType version and options the console library is built with is unknown, so the behaviour and error codes are those of FreeType 2.13.3. The OpenOrbis font, keyboard, threading, system and SDL2 samples import these six functions +- [FT_New_Memory_Face, FT_Done_Face and FT_Done_FreeType](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; they follow the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h) with LP64 guest arguments. A font buffer size outside the host `FT_Long` range throws, excluding buffers of 2 GiB or more on Windows. Caller-owned font bytes must remain valid until the face is freed. + ### Functional -- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - the guest reads copies of the bundled FreeType's face, size and glyph slot, updated after each call that changes them. Their private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) are null, and `subglyphs` is null whatever `num_subglyphs` reports. The outline points are copied as 64-bit coordinates; changes the guest makes to them and to the outline flags are written back before `FT_Render_Glyph`, while replacing the outline arrays, or a coordinate the host `FT_Pos` cannot hold, throws. `FT_New_Face` resolves the guest path and reads the whole file into memory; on Windows, where FreeType's `long` is 32 bits, a font of 2 GiB or more throws. Libraries and faces are never freed, since `FT_Done_Face` and `FT_Done_FreeType` are not exported. The bundled FreeType's own functions are kept out of the exports with `--exclude-libs` on Windows and Linux only; on macOS they stay exported, so a guest import of another FreeType function could resolve to them instead of failing at start-up +- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - the guest reads copies of the bundled FreeType's face, size and glyph slot, updated after each call that changes them. Their private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) are null, and `subglyphs` is null whatever `num_subglyphs` reports. The outline points are copied as 64-bit coordinates; changes the guest makes to them and to the outline flags are written back before `FT_Render_Glyph`, while replacing the outline arrays, or a coordinate the host `FT_Pos` cannot hold, throws. `FT_New_Face` resolves the guest path and reads the whole file into memory; on Windows, where FreeType's `long` is 32 bits, a font of 2 GiB or more throws. The bundled FreeType's own functions are kept out of the exports with `--exclude-libs` on Windows and Linux only; on macOS they stay exported, so a guest import of another FreeType function could resolve to them instead of failing at start-up From 303f15c0788362efafc4194de7ca7975e0e1fa37 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Wed, 21 Oct 2026 15:19:49 +0530 Subject: [PATCH 2/2] docs(libSceFreeType): clarify Windows memory face limits --- docs/dev/debt/libSceFreeType.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index 19872daed2..6d4f98438d 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -6,8 +6,10 @@ - [FT_Init_FreeType, FT_New_Face, FT_Set_Pixel_Sizes, FT_Get_Char_Index, FT_Load_Glyph, FT_Render_Glyph](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the PS4 library is taken to be FreeType 2 built for LP64: the face, size, charmap, glyph slot, bitmap and outline layouts follow the public headers of the bundled FreeType 2.13.3 with a 64-bit `long`, which matches the offsets the OpenOrbis samples read (`face->glyph` at 0x98, `slot->advance` at 0x80, `slot->bitmap` at 0x98, `slot->bitmap_left` at 0xc0). Which FreeType version and options the console library is built with is unknown, so the behaviour and error codes are those of FreeType 2.13.3. The OpenOrbis font, keyboard, threading, system and SDL2 samples import these six functions -- [FT_New_Memory_Face, FT_Done_Face and FT_Done_FreeType](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; they follow the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h) with LP64 guest arguments. A font buffer size outside the host `FT_Long` range throws, excluding buffers of 2 GiB or more on Windows. Caller-owned font bytes must remain valid until the face is freed. +- [FT_New_Memory_Face, FT_Done_Face and FT_Done_FreeType](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; they follow the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h) with LP64 guest arguments. Caller-owned font bytes must remain valid until the face is freed. ### Functional -- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - the guest reads copies of the bundled FreeType's face, size and glyph slot, updated after each call that changes them. Their private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) are null, and `subglyphs` is null whatever `num_subglyphs` reports. The outline points are copied as 64-bit coordinates; changes the guest makes to them and to the outline flags are written back before `FT_Render_Glyph`, while replacing the outline arrays, or a coordinate the host `FT_Pos` cannot hold, throws. `FT_New_Face` resolves the guest path and reads the whole file into memory; on Windows, where FreeType's `long` is 32 bits, a font of 2 GiB or more throws. The bundled FreeType's own functions are kept out of the exports with `--exclude-libs` on Windows and Linux only; on macOS they stay exported, so a guest import of another FreeType function could resolve to them instead of failing at start-up +- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) and `subglyphs` stay null. Face, size and glyph-slot records are copies. Only edits to outline points and flags are written back before `FT_Render_Glyph`; replacing arrays or exceeding the host `FT_Pos` range throws. `FT_New_Face` reads the whole file into memory and throws for files of 2 GiB or more on Windows. On macOS, native FreeType symbols remain exported and could resolve guest imports without adaptation; Windows and Linux hide them with `--exclude-libs`. + +- [FT_New_Memory_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - on Windows, the host FT_Long is 32 bits, so buffer sizes of 2 GiB or more or below -2 GiB throw before the font buffer is read