Files
AnyPS5/core/libs/tests/GuestPlayGo.cpp
T
Cesar RodriguezandClaude Opus 5.5 c43af1f372 fix(libScePlayGo): report optional chunks as supported and installed
A dump is fully installed, so scePlayGoGetSupportedOptionalChunk and
scePlayGoGetOptionalChunk report every optional chunk (language pack) like
scePlayGoGetLanguageMask reports every language. They returned an empty
bitmask, so PPSA01748 hid every text language its playgo-scenario.json
lists as a chunk language, English included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:30:55 +00:00

194 lines
8.5 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <initializer_list>
#include <random>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
extern "C" {
int APS5_VABI scePlayGoInitialize(const PlayGoInitParams*);
int APS5_VABI scePlayGoOpen(int*, const void*);
int APS5_VABI scePlayGoClose(int);
int APS5_VABI scePlayGoGetLanguageMask(int, std::uint64_t*);
int APS5_VABI scePlayGoGetSupportedOptionalChunk(int, std::int32_t, PlayGoOptionalChunk*);
int APS5_VABI scePlayGoGetOptionalChunk(int, std::int32_t, PlayGoOptionalChunk*);
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(); }
template <typename Operation>
static void RequireMissingChunkMetadata(Operation&& operation) {
bool threw = false;
try {
operation();
} catch (const std::runtime_error& error) {
const std::string_view message(error.what());
threw = message.find("playgo-chunk.dat") != std::string_view::npos &&
message.find("playgo-scenario.json") != std::string_view::npos &&
message.find("chunk ID") != std::string_view::npos;
}
Require(threw);
}
static int CheckPackageChunkTable(const char* root) {
constexpr int badChunkId = static_cast<int>(0x80B2000C);
std::filesystem::current_path(root);
PlayGoInitParams init{};
int handle = 0;
Require(scePlayGoInitialize(&init) == 0);
Require(scePlayGoOpen(&handle, nullptr) == 0);
const std::uint16_t known[3] = {0, 1, 2};
std::int8_t loci[3] = {0, 0, 0};
Require(scePlayGoGetLocus(handle, known, 3, loci) == 0 && loci[0] == 3 && loci[1] == 3 && loci[2] == 3);
const std::uint16_t outside[2] = {1, 3};
Require(scePlayGoGetLocus(handle, outside, 2, loci) == badChunkId);
std::uint32_t chunkCount = 0;
Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 3);
Require(scePlayGoClose(handle) == 0);
return 0;
}
static int CheckScenarioWithoutChunkTable(const char* root) {
std::filesystem::current_path(root);
PlayGoInitParams init{};
int handle = 0;
Require(scePlayGoInitialize(&init) == 0);
Require(scePlayGoOpen(&handle, nullptr) == 0);
const std::uint16_t anyIds[3] = {1, 10, 0xFFFF};
std::int8_t loci[3] = {0, 0, 0};
Require(scePlayGoGetLocus(handle, anyIds, 3, loci) == 0 && loci[0] == 3 && loci[1] == 3 && loci[2] == 3);
const PlayGoToDo todo[2] = {{0, 3, 0}, {0xFFFF, 3, 0}};
Require(scePlayGoSetToDoList(handle, todo, 2) == 0);
std::uint32_t chunkCount = 0;
Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 1);
Require(scePlayGoClose(handle) == 0);
return 0;
}
static int CheckNoPlayGoFile(const char* root) {
std::filesystem::current_path(root);
constexpr int badHandle = static_cast<int>(0x80B20009);
constexpr int badPointer = static_cast<int>(0x80B2000A);
constexpr int badSize = static_cast<int>(0x80B2000B);
constexpr int badLocus = static_cast<int>(0x80B20010);
PlayGoInitParams init{};
int handle = 0;
Require(scePlayGoInitialize(nullptr) == badPointer);
Require(scePlayGoInitialize(&init) == 0);
Require(scePlayGoOpen(nullptr, nullptr) == badPointer);
Require(scePlayGoOpen(&handle, nullptr) == 0);
std::uint64_t mask = 0;
Require(scePlayGoGetLanguageMask(handle + 1, &mask) == badHandle);
Require(scePlayGoGetLanguageMask(handle, nullptr) == badPointer);
Require(scePlayGoGetLanguageMask(handle, &mask) == 0 && mask == ~0ull);
for (const auto getOptionalChunk : {scePlayGoGetSupportedOptionalChunk, scePlayGoGetOptionalChunk}) {
PlayGoOptionalChunk option{};
Require(getOptionalChunk(handle + 1, 0, &option) == badHandle);
Require(getOptionalChunk(handle, 0, nullptr) == badPointer);
Require(getOptionalChunk(handle, 0, &option) == 0 && option.bitmask == ~0ull);
}
PlayGoToDo todo[2] = {{0, 3, 0}, {0, 0, 0}};
Require(scePlayGoSetToDoList(handle + 1, todo, 2) == badHandle);
Require(scePlayGoSetToDoList(handle, nullptr, 2) == badPointer);
Require(scePlayGoSetToDoList(handle, todo, 0) == badSize);
Require(scePlayGoSetToDoList(handle, todo, 2) == 0);
todo[1].locus = 1;
Require(scePlayGoSetToDoList(handle, todo, 2) == badLocus);
todo[1] = {1, 3, 0};
RequireMissingChunkMetadata([&] { scePlayGoSetToDoList(handle, todo, 2); });
todo[1] = {0xFFFF, 3, 0};
RequireMissingChunkMetadata([&] { scePlayGoSetToDoList(handle, todo, 2); });
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};
RequireMissingChunkMetadata([&] { scePlayGoGetLocus(handle, undeclared, 2, loci); });
const std::uint16_t onlyDeclared[2] = {0, 0};
Require(scePlayGoGetLocus(handle, onlyDeclared, 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);
return 0;
}
static int CheckRejectedLayout(const char* root, const char* fileName) {
std::filesystem::current_path(root);
PlayGoInitParams init{};
int handle = 0;
Require(scePlayGoInitialize(&init) == 0);
Require(scePlayGoOpen(&handle, nullptr) == 0);
const std::uint16_t chunk = 0;
std::int8_t locus = 0;
bool threw = false;
try {
scePlayGoGetLocus(handle, &chunk, 1, &locus);
} catch (const std::runtime_error& error) {
threw = std::string_view(error.what()).find(fileName) != std::string_view::npos;
}
Require(threw);
return 0;
}
static void RunLayout(const char* self, const char* mode, std::initializer_list<std::pair<const char*, std::string>> files) {
const auto root = std::filesystem::temp_directory_path() / ("guest_playgo_" + std::to_string(std::random_device{}()));
std::filesystem::remove_all(root);
std::filesystem::create_directories(root / "app0" / "sce_sys");
for (const auto& [name, content] : files) std::ofstream(root / "app0" / "sce_sys" / name, std::ios::binary).write(content.data(), static_cast<std::streamsize>(content.size()));
const std::string arguments = "\"" + std::string(self) + "\" " + mode + " \"" + root.string() + "\"";
#ifdef _WIN32
const std::string command = "\"" + arguments + "\"";
#else
const std::string command = arguments;
#endif
const int status = std::system(command.c_str());
std::filesystem::remove_all(root);
Require(status == 0);
}
static std::string ChunkTableFile() {
std::string header(0x100, '\0');
header.replace(0, 4, "plgx");
header[0x0A] = 3;
return header;
}
int main(int argc, char** argv) {
if (argc == 3) {
const std::string_view mode = argv[1];
if (mode == "--no-file") return CheckNoPlayGoFile(argv[2]);
if (mode == "--package-chunk-table" || mode == "--scenario-with-chunk-table") return CheckPackageChunkTable(argv[2]);
if (mode == "--scenario-only") return CheckScenarioWithoutChunkTable(argv[2]);
if (mode == "--ficm-only") return CheckRejectedLayout(argv[2], "playgo-ficm.dat");
if (mode == "--hash-table-only") return CheckRejectedLayout(argv[2], "playgo-hash-table.dat");
if (mode == "--invalid-chunk-table") return CheckRejectedLayout(argv[2], "playgo-chunk.dat");
}
RunLayout(argv[0], "--no-file", {});
RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}});
RunLayout(argv[0], "--scenario-with-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}, {"playgo-scenario.json", "{}"}});
RunLayout(argv[0], "--scenario-only", {{"playgo-scenario.json", "{}"}});
RunLayout(argv[0], "--ficm-only", {{"playgo-ficm.dat", "x"}});
RunLayout(argv[0], "--hash-table-only", {{"playgo-hash-table.dat", "x"}});
RunLayout(argv[0], "--invalid-chunk-table", {{"playgo-chunk.dat", "not a chunk table"}});
return 0;
}