Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/libs/prx/libSceFont/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ if(BUILD_TESTING)
add_test_executable(guest_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestFont.cpp)
target_include_directories(guest_font_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc)
configure_windows_unwind(guest_font_tests)
add_test(NAME guest_font COMMAND guest_font_tests)
add_test_executable(guest_system_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemFont.cpp)
target_include_directories(guest_system_font_tests PRIVATE ${LIBS_INCLUDE_DIR})
Expand Down
10 changes: 10 additions & 0 deletions core/libs/prx/libSceFont/include/FontTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,15 @@ struct FontWritingLetterStep {
} Components;
};

union FontTextCodes {
struct {
void* order;
std::uint32_t code;
std::uint32_t reserved;
} text;
void* systemUse[8];
};

struct FontWritingExtent {
float top;
float bottom;
Expand Down Expand Up @@ -441,6 +450,7 @@ static_assert(sizeof(FontWriting) == 0x100);
static_assert(sizeof(FontWritingProfile) == sizeof(std::uint32_t));
static_assert(sizeof(FontWritingStep) == 0x48);
static_assert(sizeof(FontWritingLetterStep) == 0x40);
static_assert(sizeof(FontTextCodes) == 0x40);
static_assert(sizeof(FontWritingMetrics) == 0x18);
static_assert(sizeof(FontWritingLineStep) == 0x40);
static_assert(offsetof(FontWritingLineStep, writingOrderer) == 0x18);
Expand Down
47 changes: 47 additions & 0 deletions core/libs/prx/libSceFont/src/Text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ void AccumulateWritingMetrics(FontWritingData& writing) {
writing.metrics.Extent.bottom = std::max(previous.Extent.bottom, bottom);
}

constexpr std::size_t TEXT_CODES_CURRENT = 5;
constexpr std::size_t TEXT_CODES_START = 6;
constexpr std::size_t TEXT_CODES_TERM = 7;

void StepTextCodes(FontTextCodes* textCodes, const FontTextCharacter* character) {
textCodes->text.order = character->textOrder;
textCodes->text.code = character->characterCode;
textCodes->systemUse[TEXT_CODES_CURRENT] = const_cast<FontTextCharacter*>(character);
}

const FontTextCharacter* TextCodesCharacter(const FontTextCodes* textCodes, std::size_t slot) {
return static_cast<const FontTextCharacter*>(textCodes->systemUse[slot]);
}

}

#pragma GCC visibility push(default)
Expand Down Expand Up @@ -551,6 +565,39 @@ FontTextCharacter* APS5_VABI sceFontCharacterRefersTextNext(const FontTextCharac
return nullptr;
}

FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter* textCharacter, const FontTextCharacter* termCharacter, FontTextCodes* textCodes) {
if (!textCodes) return nullptr;
if (!textCharacter) throw std::runtime_error("libSceFont: sceFontCharactersRefersTextCodes without a text character");
if (textCharacter->clusterIndex != 0 || textCharacter->synthetic != 0) return nullptr;
std::memset(textCodes, 0, sizeof(FontTextCodes));
StepTextCodes(textCodes, textCharacter);
textCodes->systemUse[TEXT_CODES_START] = const_cast<FontTextCharacter*>(textCharacter);
textCodes->systemUse[TEXT_CODES_TERM] = const_cast<FontTextCharacter*>(termCharacter);
return textCodes;
}

FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* textCodesStep) {
if (!textCodesStep) return nullptr;
const FontTextCharacter* current = TextCodesCharacter(textCodesStep, TEXT_CODES_CURRENT);
if (!current || current == TextCodesCharacter(textCodesStep, TEXT_CODES_TERM)) return nullptr;
const FontTextCharacter* next = current->next;
while (next && next->synthetic != 0) next = next->next;
if (!next) return nullptr;
StepTextCodes(textCodesStep, next);
return textCodesStep;
}

FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* textCodesStep) {
if (!textCodesStep) return nullptr;
const FontTextCharacter* current = TextCodesCharacter(textCodesStep, TEXT_CODES_CURRENT);
if (!current || current == TextCodesCharacter(textCodesStep, TEXT_CODES_START)) return nullptr;
const FontTextCharacter* previous = current->prev;
while (previous && previous->synthetic != 0) previous = previous->prev;
if (!previous) return nullptr;
StepTextCodes(textCodesStep, previous);
return textCodesStep;
}

int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString, const FontTextCharacter* fontCharacter) {
if (!fontWriting || !fontString || !fontCharacter) return SCE_FONT_ERROR_INVALID_PARAMETER;
auto* data = GetStringData(fontString);
Expand Down
15 changes: 0 additions & 15 deletions core/libs/prx/libSceFont/src/Unimplemented.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@

extern "C" {

int APS5_VABI sceFontCharactersRefersTextCodes() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceFontControl() {
NotImplemented_nid_no_patch(__func__);
return 0;
Expand Down Expand Up @@ -358,16 +353,6 @@ int APS5_VABI sceFontSupportGlyphs() {
return 0;
}

int APS5_VABI sceFontTextCodesStepBack() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceFontTextCodesStepNext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceFontWordsFindWordCharacters() {
NotImplemented_nid_no_patch(__func__);
return 0;
Expand Down
60 changes: 60 additions & 0 deletions core/libs/tests/GuestFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <initializer_list>
#include <map>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>

Expand Down Expand Up @@ -56,6 +58,9 @@ FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting
const void* APS5_VABI sceFontSelectLibraryFt(int);
const void* APS5_VABI sceFontSelectRendererFt(int);
int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*);
FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter*, const FontTextCharacter*, FontTextCodes*);
FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes*);
FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes*);
}

static void Check(bool value, int line) {
Expand Down Expand Up @@ -153,6 +158,60 @@ static std::int32_t APS5_VABI ParseText(FontTextSource* source, void** order, Fo
return 1;
}

static void CheckTextCodes(const FontMemory* memory, FontHandle font) {
ParsedText parsed{"AVA", 0, font};
FontTextSource source{};
Require(sceFontTextSourceInit(&source, parsed.text, 3, ParseText, &parsed) == SCE_FONT_OK);
FontString string = nullptr;
Require(sceFontCreateString(memory, &source, nullptr, &string) == SCE_FONT_OK && string != nullptr);
std::uint32_t count = 0;
FontTextCharacter* characters = sceFontStringRefersTextCharacters(string, &count);
Require(characters != nullptr && count == 3);

FontTextCodes codes;
std::memset(&codes, 0xa5, sizeof(codes));
Require(sceFontCharactersRefersTextCodes(characters, nullptr, &codes) == &codes);
Require(codes.text.order == parsed.text && codes.text.code == 'A' && codes.text.reserved == 0);
Require(codes.systemUse[5] == characters && codes.systemUse[6] == characters && codes.systemUse[7] == nullptr);
Require(sceFontTextCodesStepNext(&codes) == &codes && codes.text.code == 'V' && codes.text.order == parsed.text + 1);
Require(sceFontTextCodesStepNext(&codes) == &codes && codes.text.code == 'A' && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepNext(&codes) == nullptr && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepBack(&codes) == &codes && codes.text.code == 'V' && codes.text.order == parsed.text + 1);
Require(sceFontTextCodesStepBack(&codes) == &codes && codes.text.code == 'A' && codes.text.order == parsed.text);
Require(sceFontTextCodesStepBack(&codes) == nullptr && codes.text.order == parsed.text);

Require(sceFontCharactersRefersTextCodes(characters + 1, characters + 2, &codes) == &codes && codes.text.code == 'V');
Require(sceFontTextCodesStepNext(&codes) == &codes && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepNext(&codes) == nullptr && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepBack(&codes) == &codes && codes.text.order == parsed.text + 1);
Require(sceFontTextCodesStepBack(&codes) == nullptr && codes.text.order == parsed.text + 1);

Require(sceFontCharactersRefersTextCodes(characters + 2, characters + 2, &codes) == &codes && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepNext(&codes) == nullptr && sceFontTextCodesStepBack(&codes) == nullptr);

characters[1].synthetic = 1;
Require(sceFontCharactersRefersTextCodes(characters, nullptr, &codes) == &codes);
Require(sceFontTextCodesStepNext(&codes) == &codes && codes.text.order == parsed.text + 2);
Require(sceFontTextCodesStepBack(&codes) == &codes && codes.text.order == parsed.text);
Require(sceFontCharactersRefersTextCodes(characters + 1, nullptr, &codes) == nullptr && codes.text.order == parsed.text);
characters[1].synthetic = 0;
characters[1].clusterIndex = 1;
Require(sceFontCharactersRefersTextCodes(characters + 1, nullptr, &codes) == nullptr && codes.text.order == parsed.text);
Require(sceFontTextCodesStepNext(&codes) == &codes && codes.text.order == parsed.text + 1);
characters[1].clusterIndex = 0;

Require(sceFontCharactersRefersTextCodes(characters, nullptr, nullptr) == nullptr);
Require(sceFontTextCodesStepNext(nullptr) == nullptr && sceFontTextCodesStepBack(nullptr) == nullptr);
bool threw = false;
try {
sceFontCharactersRefersTextCodes(nullptr, nullptr, &codes);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw && codes.text.order == parsed.text + 1);
Require(sceFontDestroyString(&string) == SCE_FONT_OK);
}

static bool KerningIs(const FontKerning& kerning, float offsetX) {
return kerning.offsetX == offsetX && kerning.offsetY == 0.0f && kerning.positionX == 0.0f && kerning.positionY == 0.0f;
}
Expand Down Expand Up @@ -363,6 +422,7 @@ int main() {
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);
CheckTextCodes(&memory, font);
Require(sceFontCloseFont(font) == SCE_FONT_OK);

Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK && renderer == nullptr);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libSceFont.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [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
- [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.
- [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext and sceFontTextCodesStepBack](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signatures follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L679) IDA type database and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFont.Engine.cs#L395-L406), and the 64-byte `FontTextCodes` with the order pointer at 0 and the code at 8 follows SharpProspero's [SceFontTextCodes](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFontStructs.cs#L216-L228). The traversal follows the PS4 11.00 `libSceFont.sprx` disassembly posted in [#4050](https://github.com/boykopovar/AnyPS5/pull/4050#issuecomment-6114504202), not a PS5 measurement: the cursor keeps the current, start and term characters at +0x28, +0x30 and +0x38; `Refers` never compares the character with the term and returns null for a clustered or synthetic character; `StepNext` returns null at the term after visiting it; `StepBack` returns null when the current character is the start; both steps skip synthetic characters. Assumed and not covered by the report, which says `StepNext` returns null only at the term: `StepNext` also returns null at the end of the chain (for example with a null term), and `StepBack` at the head of the chain. A null cursor or output returns null as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1696-L1740); a null text character throws, as its console result is unknown

### Functional

Expand Down
Loading