Merge pull request #15050 from noamroger/claude/project-thread-od3mt9

feat(libSceFreeTypeOt): read Mac resource fork headers and data offsets
This commit is contained in:
Dean authored and GitHub committed 2026-11-05 21:46:48 +13:00
commit f09613011b
7 files changed
+376 -11

No files matched your search

@@ -60,3 +60,20 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOt>")
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:$<TARGET_FILE_DIR:libSceFreeTypeOt>")
endif()
endif()
-10
View File
@@ -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__);
}
@@ -0,0 +1,28 @@
// 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"
#include <cstdint>
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);
}
@@ -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"
@@ -0,0 +1,142 @@
// 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"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
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<std::int64_t>::max();
constexpr std::int64_t LongMin = std::numeric_limits<std::int64_t>::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<std::int64_t>(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<std::uint64_t>(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<std::uint64_t>(mapPos));
if (error) return error;
head2[15] = static_cast<std::uint8_t>(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<std::uint64_t>(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<std::uint64_t>(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<std::uint64_t>(Add(refList, mapOffset, function)));
if (error) return error;
if (!memory) NotImplemented_nid_no_patch(function);
auto* ref = static_cast<GuestRForkRef*>(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<std::uint64_t>(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<std::int64_t*>(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<std::int64_t>(ref[index].offset), function);
}
if (!offsets) NotImplemented_nid_no_patch(function);
*offsets = result;
}
ft_mem_free_nid_postfix(memory, ref);
return error;
}
return CannotOpenResource;
}
}
+185
View File
@@ -0,0 +1,185 @@
#include "prx/libSceFreeTypeOt/include/Memory.hpp"
#include "prx/libSceFreeTypeOt/include/Raccess.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
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<Recorder*>(memory->user)->allocs;
return std::malloc(static_cast<std::size_t>(size));
}
void APS5_VABI RecordFree(GuestMemoryRec* memory, void* block) {
++static_cast<Recorder*>(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<std::size_t>(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<std::uint8_t>& bytes, std::size_t at, std::uint32_t value) {
bytes[at] = static_cast<std::uint8_t>(value >> 8);
bytes[at + 1] = static_cast<std::uint8_t>(value);
}
void Put32(std::vector<std::uint8_t>& bytes, std::size_t at, std::uint32_t value) {
Put16(bytes, at, value >> 16);
Put16(bytes, at + 2, value);
}
struct Fork {
std::vector<std::uint8_t> 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<std::uint8_t>& 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<std::int64_t>(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<std::uint64_t>(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(&copy.library, &copy.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<std::uint8_t> 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;
}
+3 -1
View File
@@ -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.
@@ -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.