From e127a525d15db01d081f941edf74dca68305b0e3 Mon Sep 17 00:00:00 2001 From: JAB-dev <68183649+JAB-dev@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:34:20 +0200 Subject: [PATCH] feat(libSceFont): implement sceFontCharacterGetSyllableStringState The call was an unimplemented stub. It now writes syllable state 0 and returns OK for every character outside the Thai block, where a PS4 firmware 11.00 libSceFont measures 1, 3 or 4; Thai characters throw as not implemented. A null character or result returns SCE_FONT_ERROR_INVALID_PARAMETER, and a null character writes 0 to a non-null result, as measured. The signature follows SharpProspero. Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceFont/src/Text.cpp | 11 +++++++++++ core/libs/prx/libSceFont/src/Unimplemented.cpp | 5 ----- core/libs/tests/GuestFont.cpp | 8 ++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp index 5a43ff63f..791c64491 100644 --- a/core/libs/prx/libSceFont/src/Text.cpp +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "prx/libSceFont/include/FontInternal.hpp" @@ -491,6 +492,16 @@ int APS5_VABI sceFontCharacterGetBidiLevel(const FontTextCharacter* textCharacte return SCE_FONT_OK; } +int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter* textCharacter, int* syllableStringState) { + if (!textCharacter || !syllableStringState) { + if (syllableStringState) *syllableStringState = 0; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + if (textCharacter->characterCode >= 0x0E00 && textCharacter->characterCode <= 0x0E7F) throw std::runtime_error("libSceFont: sceFontCharacterGetSyllableStringState for Thai syllable clusters is not implemented"); + *syllableStringState = 0; + return SCE_FONT_OK; +} + int APS5_VABI sceFontCharacterGetTextFontCode(const FontTextCharacter* textCharacter, FontHandle* pFontHandle, std::uint32_t* textCode) { if (!textCharacter) { if (pFontHandle) *pFontHandle = nullptr; diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index 53b3e2ec9..b3c2e79b7 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -7,11 +7,6 @@ extern "C" { -int APS5_VABI sceFontCharacterGetSyllableStringState() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontCharactersRefersTextCodes() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f87745e78..9a61f20a3 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -47,6 +47,7 @@ int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph, std::uint32_t, std::uint64_ int APS5_VABI sceFontDeleteGlyph(const FontMemory*, FontGlyph*); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); +int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*); } static void Check(bool value, int line) { @@ -317,5 +318,12 @@ int main() { Require(allocations == 0); Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); Require(sceFontMemoryTerm(&memory) == SCE_FONT_ERROR_INVALID_MEMORY); + FontTextCharacter character{}; + character.characterCode = 'A'; + int syllableState = -1; + Require(sceFontCharacterGetSyllableStringState(&character, &syllableState) == SCE_FONT_OK && syllableState == 0); + syllableState = -1; + Require(sceFontCharacterGetSyllableStringState(nullptr, &syllableState) == SCE_FONT_ERROR_INVALID_PARAMETER && syllableState == 0); + Require(sceFontCharacterGetSyllableStringState(&character, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); std::filesystem::remove_all(fontRoot); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..99de3967c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -155,6 +155,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAgcBranchPatchSetThenTarget_0300, sceAgcBranchPatchSetElseTarget_0300](../../core/libs/prx/libSceAgc/Patch/src/Branch.cpp) (libSceAgc) - names from their NIDs (`7Wa3aeJgeVU`, `rP5xLdOf26k`); the `(packet, cachePolicy, target, sizeInDwords)` signature is inferred from PPSA28416's calls (a branch packet, 0, an aligned target, 4), with the policy stored where `sceAgcCbBranch` puts it - [sceAgcDcbSet{Cx,Sh,Uc}RegistersIndirectGetSize](../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - the count argument is ignored: PPSA28416 calls them with leftover register contents (0xf5f5039c), so the real functions likely take none; the size is 20 bytes either way - [sceAgcUpdateInterpolantMapping_0100](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - name from its NID (`k0E7vkgqAuE`); the `(regs, vs, ps)` arguments are the declared ones, behaviour unknown +- [sceFontCharacterGetSyllableStringState](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signature `(textCharacter, int* syllableStringState)` follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs). Measured on PS4 firmware 11.00 `libSceFont.sprx` (not on a PS5): the state is 0 for Latin, CJK, Hangul, Arabic, Hebrew, Devanagari and the other scripts tried, and the library sets 1, 3 or 4 (never 2 on real text) for Thai while it creates the string; a null character or a null result returns `SCE_FONT_ERROR_INVALID_PARAMETER`, and a null character writes 0 to a non-null result. This returns 0 for every character outside the Thai block (U+0E00 to U+0E7F), as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1805-L1815) does, and throws for Thai characters, whose cluster states are not modelled. SharpProspero's `SceFontSyllableString` names 0 NonDetection, 1 Starting, 2 Following, 3 Depending and 4 Connecting. PPSA06699 (Hot Shots Golf 2) calls it. - [sceAgcWriteDataPatchSetDst, sceAgcWriteDataPatchSetCachePolicy and their Async forms](../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - the signatures `(packet, uint8 value)` are inferred from the other patch functions, as no public declaration was found; the value is taken in the encoding of the `dst` and `cachePolicy` arguments of `sceAgcDcbWriteData` (graphics) and `sceAgcAcbWriteData` (Async, compute), and the patched packet is the one those calls would write. `sceAgcAsyncWriteDataPatchSetAddressOrOffset` is assumed to patch the address like `sceAgcWriteDataPatchSetAddressOrOffset`, as the compute WRITE_DATA packet keeps it in the same dwords - [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping_0100`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [sceAgcGetDataPacketPayloadRange](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - signature, the `{base, size in bytes}` output and the type 0 range (header + 1, one dword longer than the type 1 payload) from KytyPS5 only, matching `sceAgcGetDataPacketPayloadAddress_0090`; prosper returns header + 2 for both types from that address function