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
34 changes: 22 additions & 12 deletions core/libs/prx/libScePlayGo/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@ static constexpr int SCE_PLAYGO_ERROR_BAD_SIZE = static_cast<int>(0x80B2000B);
static constexpr int SCE_PLAYGO_ERROR_BAD_LOCUS = static_cast<int>(0x80B20010);

// The package's chunk table is not part of the dump, so the chunk set comes from the title's
// playgo-chunkdefs.xml: every listed chunk plus chunks 0 through the default chunk. Games probe
// chunk IDs and size arrays from the result, so unknown IDs must be rejected.
static const std::set<uint16_t>& ValidChunks() {
static const std::set<uint16_t> chunks = [] {
std::set<uint16_t> result{0};
// playgo-chunkdefs.xml: every listed chunk plus chunks 0 through the default chunk. When that file
// exists, IDs outside the set are rejected; without it the table is unknown and every ID is accepted.
struct ChunkTable {
std::set<uint16_t> ids{0};
bool defined = false;
};

static const ChunkTable& Chunks() {
static const ChunkTable chunks = [] {
ChunkTable table;
std::ifstream file(ResolvePath_nid_no_patch("/app0/playgo-chunkdefs.xml"));
if (!file) return result;
if (!file) return table;
table.defined = true;
auto& result = table.ids;
const std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
static const std::regex chunk(R"re(<chunk\s+id="(\d+)")re");
for (auto it = std::sregex_iterator(text.begin(), text.end(), chunk); it != std::sregex_iterator(); ++it)
Expand All @@ -42,17 +49,21 @@ static const std::set<uint16_t>& ValidChunks() {
const auto last = std::stoul(match[1].str());
for (unsigned long id = 0; id <= last && id <= 0xFFFF; ++id) result.insert(static_cast<uint16_t>(id));
}
return result;
return table;
}();
return chunks;
}

static bool IsKnownChunk(uint16_t id) {
const auto& chunks = Chunks();
return !chunks.defined || chunks.ids.contains(id);
}

static int ValidateChunks(const uint16_t* chunk_ids, uint32_t number_of_entries) {
if (!chunk_ids) return SCE_PLAYGO_ERROR_BAD_POINTER;
if (number_of_entries == 0) return SCE_PLAYGO_ERROR_BAD_SIZE;
const auto& valid = ValidChunks();
for (uint32_t index = 0; index < number_of_entries; ++index)
if (!valid.contains(chunk_ids[index])) return SCE_PLAYGO_ERROR_BAD_CHUNK_ID;
if (!IsKnownChunk(chunk_ids[index])) return SCE_PLAYGO_ERROR_BAD_CHUNK_ID;
return 0;
}

Expand All @@ -66,7 +77,7 @@ int APS5_VABI scePlayGoClose(int handle) {
int APS5_VABI scePlayGoGetChunkId(int handle, uint16_t* out_chunk_id_list, uint32_t number_of_entries, uint32_t* out_entries) {
if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE;
if (!out_entries) return SCE_PLAYGO_ERROR_BAD_POINTER;
const auto& valid = ValidChunks();
const auto& valid = Chunks().ids;
if (!out_chunk_id_list) {
*out_entries = static_cast<uint32_t>(valid.size());
return 0;
Expand Down Expand Up @@ -183,9 +194,8 @@ int APS5_VABI scePlayGoSetToDoList(int handle, const PlayGoToDo* todo_list, uint
if (handle != PLAYGO_HANDLE) return SCE_PLAYGO_ERROR_BAD_HANDLE;
if (!todo_list) return SCE_PLAYGO_ERROR_BAD_POINTER;
if (number_of_entries == 0) return SCE_PLAYGO_ERROR_BAD_SIZE;
const auto& valid = ValidChunks();
for (uint32_t index = 0; index < number_of_entries; ++index) {
if (!valid.contains(todo_list[index].chunk_id)) return SCE_PLAYGO_ERROR_BAD_CHUNK_ID;
if (!IsKnownChunk(todo_list[index].chunk_id)) return SCE_PLAYGO_ERROR_BAD_CHUNK_ID;
const int8_t locus = todo_list[index].locus;
if (locus != PLAYGO_LOCUS_NOT_DOWNLOADED && locus != PLAYGO_LOCUS_LOCAL_SLOW && locus != PLAYGO_LOCUS_LOCAL_FAST) return SCE_PLAYGO_ERROR_BAD_LOCUS;
}
Expand Down
9 changes: 7 additions & 2 deletions core/libs/tests/GuestPlayGo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ int APS5_VABI scePlayGoGetLanguageMask(int, std::uint64_t*);
int APS5_VABI scePlayGoSetToDoList(int, const PlayGoToDo*, std::uint32_t);
int APS5_VABI scePlayGoGetToDoList(int, PlayGoToDo*, std::uint32_t, std::uint32_t*);
int APS5_VABI scePlayGoGetLocus(int, const std::uint16_t*, std::uint32_t, std::int8_t*);
int APS5_VABI scePlayGoGetChunkId(int, std::uint16_t*, std::uint32_t, std::uint32_t*);
}

static void Require(bool value) { if (!value) std::abort(); }
Expand All @@ -19,7 +20,6 @@ int main() {
constexpr int badHandle = static_cast<int>(0x80B20009);
constexpr int badPointer = static_cast<int>(0x80B2000A);
constexpr int badSize = static_cast<int>(0x80B2000B);
constexpr int badChunkId = static_cast<int>(0x80B2000C);
constexpr int badLocus = static_cast<int>(0x80B20010);

PlayGoInitParams init{};
Expand All @@ -42,14 +42,19 @@ int main() {
todo[1].locus = 1;
Require(scePlayGoSetToDoList(handle, todo, 2) == badLocus);
todo[1] = {0xFFFF, 3, 0};
Require(scePlayGoSetToDoList(handle, todo, 2) == badChunkId);
Require(scePlayGoSetToDoList(handle, todo, 2) == 0);

std::uint32_t entries = 1;
Require(scePlayGoGetToDoList(handle, todo, 2, &entries) == 0 && entries == 0);
const std::uint16_t chunk = 0;
std::int8_t locus = 0;
Require(scePlayGoGetLocus(handle, &chunk, 0, &locus) == badSize);
Require(scePlayGoGetLocus(handle, &chunk, 1, &locus) == 0 && locus == 3);
const std::uint16_t undeclared[2] = {1, 10};
std::int8_t loci[2] = {0, 0};
Require(scePlayGoGetLocus(handle, undeclared, 2, loci) == 0 && loci[0] == 3 && loci[1] == 3);
std::uint32_t chunkCount = 0;
Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 1);
Require(scePlayGoClose(handle + 1) == badHandle);
Require(scePlayGoClose(handle) == 0);
}
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceVideoOutGetOutputStatusWithVrr](../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut, NID `gWT7X8H0bYs`) - the name hashes to the imported NID. PPSA26344 version 02.002.000 passes a VideoOut handle and an output pointer at three call sites. It compares a 32-bit value at byte 4 with 2, tests bit 0x10 at byte 16, and, when set, compares low 16-bit values at bytes 48, 50 and 52 with 48, 60 and 120. The full output layout, size, field meanings and console errors are unknown. Every call throws without reading or writing caller memory.
- [getuid, geteuid, getgid, getegid and issetugid](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. The values a title sees on a console are unverified
- [sceKernelGetAppInfo](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - only `app_id` and the title ID (offset 0x10, up to 9 characters plus a terminator) are written; callers declare the structure with different sizes, so the rest is left alone. `app_id` is 1, the running title's id in libSceSystemService, which is a guess, not something measured. The error codes follow shadPS4: `EPERM` for another process, and 0 without writing anything for a null pointer.
- [libScePlayGo](../../core/libs/prx/libScePlayGo/Export.cpp) - without `playgo-chunkdefs.xml` every chunk id is reported as installed (`LOCAL_FAST`) while `scePlayGoGetChunkId` still lists only chunk 0; the console's answer for an id outside the package's chunk table is not checked.
- [sceFontSetScriptLanguage, sceFontGetScriptLanguage, sceFontSetTypographicDesign, sceFontGetTypographicDesign](../../core/libs/prx/libSceFont/src/Style.cpp) (libSceFont) - signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.cs), not confirmed for the PS5. The handle keeps the language set for each script and the feature set for each typographic design and returns them, with 0 for one never set; the console default and its valid ranges are unknown, so any value is accepted. Neither setting changes shaping or rendering yet
- [Packed pixel ancillary word](../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295, and their equality with the SampleId and Layer builtins is inferred, not measured. A value passed through a select, move or phi is rebuilt from those two fields with bits 0-7, 12-15 and 29-31 zero. That is exact only because every read of the word must be an extract inside one modelled field; any other read throws. The other bits are unmodelled and their RDNA 2 layout is unknown: the RDNA 3.5 ISA is reported to give prim type at bits 0-1, VRS rates at bits 2-5 and eye id at bit 7, which was not checked against RDNA 2
- [sceAudioPropagationSourceSetAudioPath](../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation, NID `tKSmk2JsMAA`) - `(uint64_t path, const void* data, float gain)` follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioPropagation.cs) and SAROS's call, which passes a handle from `sceAudioPropagationSourceGetAudioPath`, path data and gain 1.0. Path updates are unsupported and every call throws without reading or writing caller memory. The no-propagation model yields no path handles; path allocation, ownership, rendering and console error codes remain unknown.
Expand Down
Loading