mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
NpTrophy reads the trophy count and icons from the title's trophy package (names are encrypted and stay not implemented), PS4 v1 save data calls wrap the native v2/v3 ones, and NpWebApi library contexts, handles and push event filters work offline (service filters fail without a signed-in user). libSceAjm is built from the libSceAjm.native sources for PS4 titles. Limbo's (CUSA01664) remaining PS4 imports, including the synchronous PS4 TUS calls and the Facebook dialog calls next to the existing libraries, are exported as not implemented.
120 lines
6.4 KiB
C++
120 lines
6.4 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
int APS5_VABI sceNpTrophyCreateContext(int* context, int userId, std::uint32_t serviceLabel, std::uint64_t options);
|
|
int APS5_VABI sceNpTrophyDestroyContext(int context);
|
|
int APS5_VABI sceNpTrophyCreateHandle(int* handle);
|
|
int APS5_VABI sceNpTrophyDestroyHandle(int handle);
|
|
int APS5_VABI sceNpTrophyRegisterContext(int context, int handle, std::uint64_t options);
|
|
int APS5_VABI sceNpTrophyGetTrophyUnlockState(int context, int handle, std::uint32_t* flags, std::uint32_t* count);
|
|
int APS5_VABI sceNpTrophyUnlockTrophy(int context, int handle, int trophyId, int* platinumId);
|
|
int APS5_VABI sceNpTrophyGetGameIcon(int context, int handle, void* buffer, std::uint64_t* size);
|
|
int APS5_VABI sceNpTrophyGetTrophyIcon(int context, int handle, int trophyId, void* buffer, std::uint64_t* size);
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr int InvalidArgument = static_cast<int>(0x80551604u);
|
|
constexpr int InsufficientBuffer = static_cast<int>(0x80551605u);
|
|
constexpr int InvalidContext = static_cast<int>(0x80551609u);
|
|
constexpr int InvalidTrophyId = static_cast<int>(0x8055160au);
|
|
constexpr int AlreadyUnlocked = static_cast<int>(0x8055160cu);
|
|
constexpr int NotRegistered = static_cast<int>(0x8055160fu);
|
|
constexpr int AlreadyRegistered = static_cast<int>(0x80551610u);
|
|
constexpr int ContextAlreadyExists = static_cast<int>(0x80551613u);
|
|
constexpr int TitleConfNotInstalled = static_cast<int>(0x80551626u);
|
|
constexpr int TrophyNotUnlocked = static_cast<int>(0x8055161au);
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "NpTrophy: %s\n", message);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
void PutBigEndian(std::vector<std::uint8_t>& bytes, std::size_t at, std::uint64_t value, int width) {
|
|
for (int i = 0; i < width; ++i) bytes[at + i] = static_cast<std::uint8_t>(value >> (8 * (width - 1 - i)));
|
|
}
|
|
|
|
void WritePackage(const std::filesystem::path& path, const std::vector<std::pair<std::string, std::string>>& files) {
|
|
const std::size_t header = 0x60, entry = 0x40;
|
|
std::vector<std::uint8_t> bytes(header + entry * files.size());
|
|
PutBigEndian(bytes, 0, 0xdca24d00u, 4);
|
|
PutBigEndian(bytes, 0x10, files.size(), 4);
|
|
PutBigEndian(bytes, 0x14, entry, 4);
|
|
for (std::size_t i = 0; i < files.size(); ++i) {
|
|
const auto at = header + entry * i;
|
|
std::memcpy(bytes.data() + at, files[i].first.data(), files[i].first.size());
|
|
PutBigEndian(bytes, at + 32, bytes.size(), 8);
|
|
PutBigEndian(bytes, at + 40, files[i].second.size(), 8);
|
|
bytes.insert(bytes.end(), files[i].second.begin(), files[i].second.end());
|
|
}
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream(path, std::ios::binary).write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
const auto root = std::filesystem::temp_directory_path() / ("anyps5-np-trophy-" + std::to_string(std::random_device{}()));
|
|
std::filesystem::remove_all(root);
|
|
WritePackage(root / "app0/sce_sys/trophy/trophy00.trp", {{"TROPCONF.ESFM", "conf"}, {"ICON0.PNG", "game-icon"}, {"TROP000.PNG", "first"}, {"TROP001.PNG", "second"}});
|
|
std::filesystem::current_path(root);
|
|
|
|
int context = 0, handle = 0, missing = 0, platinum = 0;
|
|
Require(sceNpTrophyCreateContext(nullptr, 1, 0, 0) == InvalidArgument, "null context");
|
|
Require(sceNpTrophyCreateContext(&context, 1, 0, 1) == InvalidArgument, "context options");
|
|
Require(sceNpTrophyCreateContext(&context, 1, 0, 0) == 0, "create the context");
|
|
Require(sceNpTrophyCreateContext(&missing, 1, 0, 0) == ContextAlreadyExists, "second context for the same user and label");
|
|
Require(sceNpTrophyCreateHandle(&handle) == 0, "create the handle");
|
|
|
|
std::uint32_t flags[4] = {}, count = 0;
|
|
Require(sceNpTrophyGetTrophyUnlockState(context, handle, flags, &count) == NotRegistered, "state before registration");
|
|
Require(sceNpTrophyRegisterContext(context, handle, 0) == 0, "register the context");
|
|
Require(sceNpTrophyRegisterContext(context, handle, 0) == AlreadyRegistered, "register twice");
|
|
Require(sceNpTrophyGetTrophyUnlockState(context, handle, flags, &count) == 0 && count == 2 && flags[0] == 0, "two trophies, none unlocked");
|
|
|
|
std::uint64_t size = 0;
|
|
Require(sceNpTrophyGetGameIcon(context, handle, nullptr, &size) == 0 && size == 9, "game icon size");
|
|
char icon[16] = {};
|
|
size = 4;
|
|
Require(sceNpTrophyGetGameIcon(context, handle, icon, &size) == InsufficientBuffer, "game icon into a short buffer");
|
|
size = sizeof(icon);
|
|
Require(sceNpTrophyGetGameIcon(context, handle, icon, &size) == 0 && size == 9 && std::memcmp(icon, "game-icon", 9) == 0, "game icon bytes");
|
|
|
|
size = sizeof(icon);
|
|
Require(sceNpTrophyGetTrophyIcon(context, handle, 1, icon, &size) == TrophyNotUnlocked, "icon of a locked trophy");
|
|
Require(sceNpTrophyUnlockTrophy(context, handle, 2, &platinum) == InvalidTrophyId, "unlock a trophy past the count");
|
|
Require(sceNpTrophyUnlockTrophy(context, handle, 1, &platinum) == 0 && platinum == -1, "unlock trophy 1");
|
|
Require(sceNpTrophyUnlockTrophy(context, handle, 1, &platinum) == AlreadyUnlocked, "unlock trophy 1 twice");
|
|
Require(sceNpTrophyGetTrophyUnlockState(context, handle, flags, &count) == 0 && flags[0] == 2u, "trophy 1 unlocked");
|
|
size = sizeof(icon);
|
|
Require(sceNpTrophyGetTrophyIcon(context, handle, 1, icon, &size) == 0 && size == 6 && std::memcmp(icon, "second", 6) == 0, "icon of the unlocked trophy");
|
|
int saved = -1;
|
|
{
|
|
std::ifstream state(root / "_trophy/trophy00.1");
|
|
state >> saved;
|
|
}
|
|
Require(saved == 1, "unlock state saved for the user");
|
|
|
|
Require(sceNpTrophyCreateContext(&missing, 1, 1, 0) == 0, "context for a label without a package");
|
|
Require(sceNpTrophyRegisterContext(missing, handle, 0) == TitleConfNotInstalled, "register without a trophy package");
|
|
Require(sceNpTrophyDestroyContext(missing) == 0, "destroy the second context");
|
|
Require(sceNpTrophyDestroyHandle(handle) == 0 && sceNpTrophyDestroyContext(context) == 0, "destroy the handle and context");
|
|
Require(sceNpTrophyDestroyContext(context) == InvalidContext, "destroy the context twice");
|
|
|
|
std::filesystem::current_path(root.parent_path());
|
|
std::error_code error;
|
|
std::filesystem::remove_all(root, error);
|
|
return 0;
|
|
}
|