From b57627322534a5041d3893797f641278b6f9d55a Mon Sep 17 00:00:00 2001 From: JAB-dev <68183649+JAB-dev@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:48:37 +0200 Subject: [PATCH] fix(libSceFont): return the letter step of sceFontWritingRefersRenderStepCharacter through a pointer The second argument is an out pointer to the writing's letter step, as KytyPS5 and the SharpProspero bindings declare it. It was filled as a letter step struct, which wrote 64 bytes over the caller's 8-byte pointer. The letter step now lives in the writing and the call stores its address, or null when there is no current step. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceFont/src/Text.cpp | 30 ++++++++-------- core/libs/tests/GuestFont.cpp | 52 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp index 5a43ff63f..1c81e6977 100644 --- a/core/libs/prx/libSceFont/src/Text.cpp +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -63,10 +63,11 @@ struct FontWritingData { bool hasCurrentStep = false; bool maskFormatCharacters = true; bool ownsStorage = false; + bool hasRawGlyphMetrics = false; FontWritingStep step{}; FontWritingMetrics metrics{}; FontGlyphMetrics rawGlyphMetrics{}; - bool hasRawGlyphMetrics = false; + FontWritingLetterStep letterStep{}; }; static_assert(sizeof(FontWritingData) <= sizeof(FontWriting)); @@ -596,21 +597,22 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fon return &writing->step; } -FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, FontWritingLetterStep* letterStep) { - if (letterStep) std::memset(letterStep, 0, sizeof(*letterStep)); +FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, const FontWritingLetterStep** pLetterStep) { + if (pLetterStep) *pLetterStep = nullptr; auto* writing = GetWritingData(fontWriting); if (!writing || !writing->hasCurrentStep || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr; - if (letterStep) { - letterStep->x = writing->step.x; - letterStep->y = writing->step.y; - letterStep->advanceX = writing->step.advanceX; - letterStep->advanceY = writing->step.advanceY; - letterStep->Components.textsCount = 1; - letterStep->Components.textsIndex = static_cast(writing->currentIndex); - letterStep->Components.glyphsCount = 1; - letterStep->Components.glyphsIndex = static_cast(writing->currentIndex); - letterStep->Components.characterTextCount = static_cast(std::max(writing->step.Profile.characterCount, 1)); - } + auto& letterStep = writing->letterStep; + letterStep = {}; + letterStep.x = writing->step.x; + letterStep.y = writing->step.y; + letterStep.advanceX = writing->step.advanceX; + letterStep.advanceY = writing->step.advanceY; + letterStep.Components.textsCount = 1; + letterStep.Components.textsIndex = static_cast(writing->currentIndex); + letterStep.Components.glyphsCount = 1; + letterStep.Components.glyphsIndex = static_cast(writing->currentIndex); + letterStep.Components.characterTextCount = static_cast(std::max(writing->step.Profile.characterCount, 1)); + if (pLetterStep) *pLetterStep = &letterStep; return &writing->storage->characters[writing->currentIndex]; } diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f87745e78..82bcd6951 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -45,6 +45,13 @@ int APS5_VABI sceFontGetRenderScaledKerning(FontHandle, std::uint32_t, std::uint int APS5_VABI sceFontGenerateCharGlyph(FontHandle, std::uint32_t, const FontGenerateGlyphDetail*, FontGlyph*); int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph, std::uint32_t, std::uint64_t); int APS5_VABI sceFontDeleteGlyph(const FontMemory*, FontGlyph*); +int APS5_VABI sceFontTextSourceInit(FontTextSource*, const void*, std::uint32_t, FontTextParseFunction, void*); +int APS5_VABI sceFontCreateString(const FontMemory*, FontTextSource*, const FontCreateStringDetail*, FontString*); +int APS5_VABI sceFontDestroyString(FontString*); +FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString, std::uint32_t*); +int APS5_VABI sceFontWritingInit(FontWriting*, FontString, const FontTextCharacter*); +const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting*); +FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting*, const FontWritingLetterStep**); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); } @@ -126,6 +133,24 @@ static std::vector KerningFont() { }); } +struct ParsedText { + const char* text; + std::size_t at; + FontHandle font; +}; + +static std::int32_t APS5_VABI ParseText(FontTextSource* source, void** order, FontTextParseResult* result) { + auto* parsed = static_cast(source->textObject); + *order = const_cast(parsed->text + parsed->at); + if (parsed->text[parsed->at] == 0) { + result->Terminate.terminateCode = 0; + return 0; + } + result->FontCode.font = parsed->font; + result->FontCode.code = static_cast(parsed->text[parsed->at++]); + return 1; +} + static bool KerningIs(const FontKerning& kerning, float offsetX) { return kerning.offsetX == offsetX && kerning.offsetY == 0.0f && kerning.positionX == 0.0f && kerning.positionY == 0.0f; } @@ -307,6 +332,33 @@ int main() { Require(sceFontGetRenderScaledKerning(&unopened, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + + ParsedText parsed{"AV", 0, font}; + FontTextSource source{}; + Require(sceFontTextSourceInit(&source, parsed.text, 2, ParseText, &parsed) == SCE_FONT_OK); + FontString string = nullptr; + Require(sceFontCreateString(&memory, &source, nullptr, &string) == SCE_FONT_OK && string != nullptr); + std::uint32_t characterCount = 0; + FontTextCharacter* characters = sceFontStringRefersTextCharacters(string, &characterCount); + Require(characters != nullptr && characterCount == 2); + FontWriting writing{}; + Require(sceFontWritingInit(&writing, string, characters) == SCE_FONT_OK); + constexpr std::uint64_t guard = 0x5a5a5a5a5a5a5a5aull; + struct { + const FontWritingLetterStep* letter; + std::uint64_t guard; + } out{reinterpret_cast(&memory), guard}; + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr && out.guard == guard); + const FontWritingStep* step = sceFontWritingRefersRenderStep(&writing); + Require(step != nullptr); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == characters && out.letter != nullptr && out.guard == guard); + Require(out.letter->x == step->x && out.letter->advanceX == step->advanceX && out.letter->Components.textsIndex == 0 && out.letter->Components.characterTextCount == 1); + step = sceFontWritingRefersRenderStep(&writing); + Require(step != nullptr); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == characters + 1 && out.guard == guard); + Require(out.letter->x == step->x && out.letter->Components.textsIndex == 1 && out.letter->Components.glyphsIndex == 1); + Require(sceFontWritingRefersRenderStepCharacter(&writing, nullptr) == characters + 1); + Require(sceFontDestroyString(&string) == SCE_FONT_OK); Require(sceFontCloseFont(font) == SCE_FONT_OK); Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK && renderer == nullptr); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..1d59fca20 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -123,6 +123,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontGetFontGlyphsCount, sceFontGetCharGlyphCode](../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and matching the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, not confirmed on a title. The count is the face's glyph count and the glyph code its glyph index, as the library's driver reports them; a code with no glyph returns `NO_SUPPORT_GLYPH` and code 0 `NO_SUPPORT_CODE`, as `sceFontRenderCharGlyphImage` does. The argument checks and the cleared output follow `sceFontGetPixelResolution` - [sceFontGetFontResolution](../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title; the outputs are inferred from the names. The resolution is the face's units per EM and the scale pixel the driver's face scale (units per EM / 64 for FreeType), the pixel scale a font has when opened. Either output may be null, as in `sceFontGetResolutionDpi` - [sceFontCreateWritingLine, sceFontWritingLineWritesOrder](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - signatures and the `SceFontWritingLineStep`/`SceFontCreateWritingLineDetail` layouts (detail id `0x0FD5`) taken from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontStructs.cs), not confirmed on a title. The 64-bit writing attribute is read as the PS4 11.00 `libSceFont.sprx` reads it: low byte `0xFA`, embedding level in bits 8-15 (0 to 126), bit 30 and bit 31 marking space before and after the run; the PS5 library was not measured, and the names of the two bits are ours. PPSA01325 builds its attribute the same way (`0xFA | level << 8`, bits 30 and 31) and passes a null metrics pointer when its run has no metrics object. Attribute 0 is accepted and treated as `0xFA` with level 0 and no spacing bits, as before this reading; the PS4 library rejects it and what the PS5 library does with it is unknown. Any other attribute, a level above 126 and a null orderer throw: the PS4 library returns `0x80460002` for them, not confirmed for the PS5 +- [sceFontWritingRefersRenderStepCharacter](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the second argument is an out pointer to the writing's letter step, as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1882-L1896) and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings. Measured on PS4 firmware 11.00 `libSceFont.sprx` (not on a PS5): exactly 8 bytes are written, the address of a step stored inside the writing, and a null writing writes null; shadPS4 and prosper treat the argument as a struct, which the measurement contradicts. The letter step lives in the writing, so the pointer stays valid as long as the writing does, and its contents are rebuilt by each call of this function. On the PS4 library repeated calls on one step walk the characters it covers and then return null; here every call returns the step's one character, so the second call differs. Assumed: each step covers one character and one glyph. PPSA06699 (Hot Shots Golf 2) dereferences the returned pointer for its character counts. - [sceFontWritingLineGetOrderingSpace](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - the four outputs are written in the order the PS4 11.00 `libSceFont.sprx` writes them (line advance, leading space, space between spaced runs, trailing space); the parameter names in the [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) IDA type database and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings order them differently (head, inline, tail, advance), and the PS5 library was not measured. PPSA01325's only call uses them in the PS4 order: it computes first - second - fourth - 0.25 * third and compares that, divided by a scale, with its width limit. The PS4 library writes the trailing space through its pointer without a null check whenever the leading-space pointer is set; that call throws here - [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceCoredumpWriteUserString](../../core/libs/prx/libkernel/System/src/Coredump.cpp) and [sceSaveDataConvert, sceSaveDataGetConvertProgress](../../core/libs/prx/libSceSaveData.native/Export.cpp) - imported by PPSA16064, not called during its start-up and added without an implementation; unknown signatures