mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
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>
194 lines
8.5 KiB
C++
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;
|
|
}
|