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 <noreply@anthropic.com>
This commit is contained in:
JAB-dev 2026-10-09 20:34:20 +02:00
parent a11c8f7f70
commit e127a525d1
4 changed files with 20 additions and 5 deletions

View File

@ -6,6 +6,7 @@
#include <cstddef>
#include <cstring>
#include <new>
#include <stdexcept>
#include <vector>
#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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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