Merge pull request #9433 from Get-fix/ps4/limbo-libs

feat: PS4 NpTrophy, save data v1, NpWebApi offline and Limbo's other imports
This commit is contained in:
Dean authored and GitHub committed 2026-10-22 18:00:00 +13:00
commit da54c652e3
25 files changed
+1371 -13

No files matched your search

@@ -0,0 +1,15 @@
function(add_sce_ajm_library target)
set(ajmDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR})
add_library(${target} SHARED EXCLUDE_FROM_ALL
${ajmDir}/Export.cpp
${ajmDir}/src/Ajm.cpp
${ARGN}
)
target_include_directories(${target} PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src
${CMAKE_SOURCE_DIR}/3rdparty/vgmstream/src/coding/libs
)
target_link_libraries(${target} PRIVATE atrac9 atrac9_vibration ffmpeg libc libkernel)
configure_windows_unwind(${target})
endfunction()
+2 -12
View File
@@ -29,18 +29,8 @@ add_library(atrac9_vibration STATIC EXCLUDE_FROM_ALL
)
set_target_properties(atrac9_vibration PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden)
add_library(libSceAjm.native SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Ajm.cpp
)
target_include_directories(libSceAjm.native PRIVATE
${LIBS_INCLUDE_DIR}
${LIBATRAC9_DIR}
${VGMSTREAM_ATRAC9_DIR}
)
target_link_libraries(libSceAjm.native PRIVATE atrac9 atrac9_vibration ffmpeg libc libkernel)
configure_windows_unwind(libSceAjm.native)
include(${CMAKE_CURRENT_SOURCE_DIR}/AjmLibrary.cmake)
add_sce_ajm_library(libSceAjm.native)
if(BUILD_TESTING)
add_test_executable(atrac9_gradient_tests tests/Atrac9Gradient.c ${LIBATRAC9_DIR}/bit_reader.c)
+2
View File
@@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceAjm.native/AjmLibrary.cmake)
add_sce_ajm_library(libSceAjm ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
+14 -1
View File
@@ -5,6 +5,19 @@
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceAjmBatchJobControlBufferRa() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAjmBatchJobRunSplitBufferRa() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAjmBatchStartBuffer() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,31 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceGameCustomDataDialogGetResult() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGameCustomDataDialogInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGameCustomDataDialogOpen() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGameCustomDataDialogTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGameCustomDataDialogUpdateStatus() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,31 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceInvitationDialogGetResult() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceInvitationDialogInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceInvitationDialogOpen() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceInvitationDialogTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceInvitationDialogUpdateStatus() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -115,6 +115,11 @@ int APS5_VABI sceNpCloseSema(void) {
return 0;
}
int APS5_VABI sceNpCmpNpId(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpCmpNpIdInOrder(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+49
View File
@@ -981,3 +981,52 @@ int APS5_VABI sceNpCheckCallbackForLib() {
}
}
extern "C" {
int APS5_VABI sceNpInGameMessageCreateHandle() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpInGameMessageDeleteHandle() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpInGameMessageInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpInGameMessagePrepare() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpInGameMessageSendData() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpInGameMessageTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookCreateRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookGetAccessToken() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+151
View File
@@ -0,0 +1,151 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceNpMatching2ContextStart() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2ContextStop() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2CreateContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2CreateJoinRoom() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2DestroyContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetRoomDataExternalList() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetRoomDataInternal() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetRoomMemberDataInternal() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetServerId() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetUserInfoList() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2GetWorldInfoList() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2Initialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2JoinRoom() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2KickoutRoomMember() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2LeaveRoom() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2RegisterContextCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2RegisterRoomEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2RegisterRoomMessageCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2RegisterSignalingCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SearchRoom() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SendRoomChatMessage() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SendRoomMessage() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SetDefaultRequestOptParam() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SetRoomDataExternal() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SetRoomDataInternal() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SetRoomMemberDataInternal() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SetUserInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2SignalingGetConnectionInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpMatching2Terminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,81 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceNpScoreAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreCreateNpTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreCreateRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreDeleteNpTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetBoardInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetFriendsRanking() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetFriendsRankingAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetRankingByNpId() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetRankingByNpIdAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetRankingByRange() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreGetRankingByRangeAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScorePollAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreRecordScore() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpScoreRecordScoreAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,46 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceNpSignalingActivateConnection() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingCreateContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingDeleteContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingGetConnectionInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingSetContextOption() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSignalingTerminateConnection() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -15,4 +15,29 @@ int APS5_VABI sceNpSnsFacebookDialogGetStatus(void) {
return 0;
}
int APS5_VABI sceNpSnsFacebookDialogGetResult() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookDialogInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookDialogOpen() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookDialogTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpSnsFacebookDialogUpdateStatus() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,16 @@
add_library(libSceNpTrophy SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceNpTrophy PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceNpTrophy)
if(BUILD_TESTING)
add_test_executable(guest_np_trophy_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy.cpp)
target_include_directories(guest_np_trophy_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_np_trophy_tests PRIVATE libSceNpTrophy)
configure_windows_unwind(guest_np_trophy_tests)
add_test(NAME guest_np_trophy COMMAND guest_np_trophy_tests)
set_tests_properties(guest_np_trophy PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_np_trophy PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpTrophy>")
endif()
endif()
+268
View File
@@ -0,0 +1,268 @@
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <map>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int NP_TROPHY_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80551604u);
constexpr int NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = static_cast<int>(0x80551605u);
constexpr int NP_TROPHY_ERROR_INVALID_HANDLE = static_cast<int>(0x80551608u);
constexpr int NP_TROPHY_ERROR_INVALID_CONTEXT = static_cast<int>(0x80551609u);
constexpr int NP_TROPHY_ERROR_INVALID_TROPHY_ID = static_cast<int>(0x8055160au);
constexpr int NP_TROPHY_ERROR_INVALID_GROUP_ID = static_cast<int>(0x8055160bu);
constexpr int NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = static_cast<int>(0x8055160cu);
constexpr int NP_TROPHY_ERROR_NOT_REGISTERED = static_cast<int>(0x8055160fu);
constexpr int NP_TROPHY_ERROR_ALREADY_REGISTERED = static_cast<int>(0x80551610u);
constexpr int NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = static_cast<int>(0x80551613u);
constexpr int NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = static_cast<int>(0x80551614u);
constexpr int NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = static_cast<int>(0x80551616u);
constexpr int NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = static_cast<int>(0x8055161au);
constexpr int NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = static_cast<int>(0x80551626u);
constexpr int NP_TROPHY_INVALID_ID = -1;
constexpr int NP_TROPHY_NUM_MAX = 128;
constexpr std::uint32_t TrpMagic = 0xdca24d00u;
constexpr std::size_t TrpHeaderBytes = 0x60;
constexpr std::size_t TrpEntryBytes = 0x40;
struct TrophyFlagArray {
std::uint32_t bits[NP_TROPHY_NUM_MAX / 32];
};
struct TrpEntry {
std::uint64_t offset;
std::uint64_t size;
};
struct Context {
int userId;
std::uint32_t serviceLabel;
bool registered = false;
std::filesystem::path package;
std::map<std::string, TrpEntry> entries;
int trophies = 0;
std::filesystem::path state;
std::set<int> unlocked;
};
std::mutex g_mutex;
std::map<int, Context> g_contexts;
std::set<int> g_handles;
int g_nextContext = 1;
int g_nextHandle = 1;
std::uint32_t BigEndian32(const std::uint8_t* bytes) {
return (std::uint32_t{bytes[0]} << 24) | (std::uint32_t{bytes[1]} << 16) | (std::uint32_t{bytes[2]} << 8) | bytes[3];
}
std::uint64_t BigEndian64(const std::uint8_t* bytes) {
return (std::uint64_t{BigEndian32(bytes)} << 32) | BigEndian32(bytes + 4);
}
std::optional<std::map<std::string, TrpEntry>> ReadPackage(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary);
if (!file) return std::nullopt;
std::array<std::uint8_t, TrpHeaderBytes> header{};
if (!file.read(reinterpret_cast<char*>(header.data()), header.size()) || BigEndian32(header.data()) != TrpMagic) return std::map<std::string, TrpEntry>{};
const auto count = BigEndian32(header.data() + 0x10);
if (BigEndian32(header.data() + 0x14) != TrpEntryBytes) return std::map<std::string, TrpEntry>{};
std::map<std::string, TrpEntry> entries;
for (std::uint32_t i = 0; i < count; ++i) {
std::array<std::uint8_t, TrpEntryBytes> entry{};
if (!file.read(reinterpret_cast<char*>(entry.data()), entry.size())) return std::map<std::string, TrpEntry>{};
const std::string name(reinterpret_cast<const char*>(entry.data()), strnlen(reinterpret_cast<const char*>(entry.data()), 32));
entries[name] = {BigEndian64(entry.data() + 32), BigEndian64(entry.data() + 40)};
}
return entries;
}
void LoadState(Context& context) {
std::ifstream file(context.state);
int id = 0;
while (file >> id) {
if (id >= 0 && id < context.trophies) context.unlocked.insert(id);
}
}
void SaveState(const Context& context) {
std::filesystem::create_directories(context.state.parent_path());
const auto temporary = context.state.string() + ".tmp";
{
std::ofstream file(temporary, std::ios::trunc);
for (const auto id : context.unlocked) file << id << '\n';
}
std::filesystem::rename(temporary, context.state);
}
int Lookup(int contextId, int handle, Context*& context) {
if (contextId == NP_TROPHY_INVALID_ID) return NP_TROPHY_ERROR_INVALID_CONTEXT;
if (handle == NP_TROPHY_INVALID_ID) return NP_TROPHY_ERROR_INVALID_HANDLE;
const auto found = g_contexts.find(contextId);
if (found == g_contexts.end()) return NP_TROPHY_ERROR_INVALID_CONTEXT;
if (!g_handles.contains(handle)) return NP_TROPHY_ERROR_INVALID_HANDLE;
context = &found->second;
return 0;
}
int CopyIcon(const Context& context, const std::string& name, void* buffer, std::uint64_t* size) {
const auto entry = context.entries.find(name);
if (entry == context.entries.end()) return NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
if (buffer == nullptr) {
*size = entry->second.size;
return 0;
}
if (*size < entry->second.size) return NP_TROPHY_ERROR_INSUFFICIENT_BUFFER;
std::ifstream file(context.package, std::ios::binary);
file.seekg(static_cast<std::streamoff>(entry->second.offset));
if (!file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(entry->second.size))) return NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT;
*size = entry->second.size;
return 0;
}
}
extern "C" {
int APS5_VABI sceNpTrophyCreateContext(int* context, int userId, std::uint32_t serviceLabel, std::uint64_t options) {
if (!context || options != 0) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
for (const auto& [id, existing] : g_contexts) {
if (existing.userId == userId && existing.serviceLabel == serviceLabel) return NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS;
}
const int id = g_nextContext++;
g_contexts[id] = Context{userId, serviceLabel};
*context = id;
return 0;
}
int APS5_VABI sceNpTrophyDestroyContext(int context) {
std::lock_guard lock(g_mutex);
return g_contexts.erase(context) == 1 ? 0 : NP_TROPHY_ERROR_INVALID_CONTEXT;
}
int APS5_VABI sceNpTrophyCreateHandle(int* handle) {
if (!handle) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
const int id = g_nextHandle++;
g_handles.insert(id);
*handle = id;
return 0;
}
int APS5_VABI sceNpTrophyDestroyHandle(int handle) {
std::lock_guard lock(g_mutex);
return g_handles.erase(handle) == 1 ? 0 : NP_TROPHY_ERROR_INVALID_HANDLE;
}
int APS5_VABI sceNpTrophyRegisterContext(int contextId, int handle, std::uint64_t options) {
if (options != 0) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (context->registered) return NP_TROPHY_ERROR_ALREADY_REGISTERED;
char name[32];
std::snprintf(name, sizeof(name), "trophy%02u.trp", context->serviceLabel);
context->package = ResolvePath_nid_no_patch((std::string("/app0/sce_sys/trophy/") + name).c_str());
const auto entries = ReadPackage(context->package);
if (!entries) return NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED;
if (entries->empty()) return NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT;
context->entries = *entries;
for (int id = 0; id < NP_TROPHY_NUM_MAX; ++id) {
char icon[16];
std::snprintf(icon, sizeof(icon), "TROP%03d.PNG", id);
if (!context->entries.contains(icon)) break;
context->trophies = id + 1;
}
std::snprintf(name, sizeof(name), "trophy%02u.%d", context->serviceLabel, context->userId);
context->state = std::filesystem::path("_trophy") / name;
LoadState(*context);
context->registered = true;
return 0;
}
int APS5_VABI sceNpTrophyGetTrophyUnlockState(int contextId, int handle, TrophyFlagArray* flags, std::uint32_t* count) {
if (!flags || !count) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED;
std::memset(flags, 0, sizeof(*flags));
for (const auto id : context->unlocked) flags->bits[id >> 5] |= 1u << (id & 31);
*count = static_cast<std::uint32_t>(context->trophies);
return 0;
}
int APS5_VABI sceNpTrophyUnlockTrophy(int contextId, int handle, int trophyId, int* platinumId) {
if (!platinumId) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED;
if (trophyId < 0 || trophyId >= context->trophies) return NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (context->unlocked.contains(trophyId)) return NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
context->unlocked.insert(trophyId);
SaveState(*context);
*platinumId = NP_TROPHY_INVALID_ID;
return 0;
}
int APS5_VABI sceNpTrophyGetGameIcon(int contextId, int handle, void* buffer, std::uint64_t* size) {
if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED;
return CopyIcon(*context, "ICON0.PNG", buffer, size);
}
int APS5_VABI sceNpTrophyGetGroupIcon(int contextId, int handle, int groupId, void* buffer, std::uint64_t* size) {
if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED;
if (groupId < 0) return NP_TROPHY_ERROR_INVALID_GROUP_ID;
char icon[16];
std::snprintf(icon, sizeof(icon), "GR%03d.PNG", groupId);
return CopyIcon(*context, icon, buffer, size);
}
int APS5_VABI sceNpTrophyGetTrophyIcon(int contextId, int handle, int trophyId, void* buffer, std::uint64_t* size) {
if (!size) return NP_TROPHY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(g_mutex);
Context* context = nullptr;
if (const int error = Lookup(contextId, handle, context)) return error;
if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED;
if (trophyId < 0 || trophyId >= context->trophies) return NP_TROPHY_ERROR_INVALID_TROPHY_ID;
if (!context->unlocked.contains(trophyId)) return NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED;
char icon[16];
std::snprintf(icon, sizeof(icon), "TROP%03d.PNG", trophyId);
return CopyIcon(*context, icon, buffer, size);
}
int APS5_VABI sceNpTrophyGetGameInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTrophyGetGroupInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTrophyGetTrophyInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+50
View File
@@ -782,4 +782,54 @@ int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(void) {
return 0;
}
int APS5_VABI sceNpTssGetData() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusCreateNpTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusGetData() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusGetDataVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusGetMultiSlotVariable() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusGetMultiSlotVariableVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusSetData() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusSetDataVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusSetMultiSlotVariable() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusSetMultiSlotVariableVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+79
View File
@@ -38,3 +38,82 @@ int APS5_VABI sceNpBandwidthTestShutdown(int context_id, void* result) {
}
}
extern "C" {
int APS5_VABI sceNpBandwidthTestInitStart() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupDeleteTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupNpId() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterCensorComment() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterCreateAsyncRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterCreateRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterCreateTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterDeleteTitleCtx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterPollAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterSanitizeComment() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,16 @@
add_library(libSceNpWebApi SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceNpWebApi PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceNpWebApi)
if(BUILD_TESTING)
add_test_executable(guest_np_web_api_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpWebApi.cpp)
target_include_directories(guest_np_web_api_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_np_web_api_tests PRIVATE libSceNpWebApi)
configure_windows_unwind(guest_np_web_api_tests)
add_test(NAME guest_np_web_api COMMAND guest_np_web_api_tests)
set_tests_properties(guest_np_web_api PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_np_web_api PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpWebApi>")
endif()
endif()
+181
View File
@@ -0,0 +1,181 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80552902);
static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = static_cast<int>(0x80552904);
static constexpr int SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = static_cast<int>(0x8055290B);
static constexpr int SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = static_cast<int>(0x8055290D);
static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = static_cast<int>(0x8055290E);
static constexpr int SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = static_cast<int>(0x80552910);
static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = static_cast<int>(0x8055291A);
namespace {
struct LibraryContext {
int libHttpCtxId;
std::size_t poolSize;
std::unordered_set<int> handles;
std::unordered_set<int> pushEventFilters;
};
std::mutex contextsMutex;
std::unordered_map<int, LibraryContext> contexts;
int lastContext = 0;
int lastObject = 0;
int nextObject() {
lastObject = lastObject % 0xefffffff + 1;
return lastObject;
}
}
extern "C" {
int APS5_VABI sceNpWebApiAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiCreateContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiCreateHandle(int libCtxId) {
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
const auto handle = nextObject();
context->second.handles.insert(handle);
return handle;
}
int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterParam, std::size_t filterParamNum) {
if (!filterParam || filterParamNum == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
const auto filter = nextObject();
context->second.pushEventFilters.insert(filter);
return filter;
}
int APS5_VABI sceNpWebApiCreateRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, std::uint32_t npServiceLabel, const void* filterParam, std::size_t filterParamNum) {
(void)npServiceLabel;
if (!npServiceName || !filterParam || filterParamNum == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
if (!context->second.handles.contains(handleId)) return SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
return SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
}
int APS5_VABI sceNpWebApiDeleteContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiDeleteHandle(int libCtxId, int handleId) {
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
return context->second.handles.erase(handleId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
}
int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId) {
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
return context->second.pushEventFilters.erase(filterId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
int APS5_VABI sceNpWebApiDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId) {
(void)filterId;
std::lock_guard lock(contextsMutex);
if (!contexts.contains(libCtxId)) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
return SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND;
}
int APS5_VABI sceNpWebApiGetHttpResponseHeaderValue() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiGetHttpStatusCode() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize) {
std::lock_guard lock(contextsMutex);
for (int attempt = 0; attempt < 0x7fff; ++attempt) {
lastContext = lastContext % 0x7fff + 1;
if (contexts.contains(lastContext)) continue;
contexts[lastContext] = {libHttpCtxId, poolSize, {}, {}};
return lastContext;
}
return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX;
}
int APS5_VABI sceNpWebApiReadData() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiRegisterPushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiRegisterServicePushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiSendRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiTerminate(int libCtxId) {
std::lock_guard lock(contextsMutex);
return contexts.erase(libCtxId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
}
int APS5_VABI sceNpWebApiUnregisterPushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWebApiUtilityParseNpId() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -1031,3 +1031,77 @@ int APS5_VABI sceSaveDataDebugTarget() {
}
}
namespace {
struct SaveDataMountPs4 {
std::int32_t user_id;
std::int32_t pad;
const void* title_id;
const SceSaveDataDirName* dir_name;
const void* fingerprint;
std::uint64_t blocks;
std::uint32_t mount_mode;
std::uint8_t reserved[32];
};
}
extern "C" {
int APS5_VABI sceSaveDataInitialize(const void* init) {
return sceSaveDataInitialize3(init);
}
int APS5_VABI sceSaveDataMount(const SaveDataMountPs4* mount, SaveDataMountResult* mount_result) {
if (mount == nullptr || mount->dir_name == nullptr) return SAVE_DATA_ERROR_PARAMETER;
if (mount->title_id != nullptr) NotImplemented_nid_no_patch("sceSaveDataMount of another title's save data");
SaveDataMount3 converted{};
converted.user_id = mount->user_id;
converted.dir_name = mount->dir_name;
converted.blocks = mount->blocks;
converted.mount_mode = mount->mount_mode;
return sceSaveDataMount3(&converted, mount_result);
}
int APS5_VABI sceSaveDataUmount(const SaveDataMountPoint* mount_point) {
return sceSaveDataUmount2(0, mount_point);
}
int APS5_VABI sceSaveDataSetupSaveDataMemory(std::int32_t user_id, std::size_t memory_size, const SaveDataParam* param) {
SaveDataMemorySetup2 setup{};
setup.user_id = user_id;
setup.memory_size = memory_size;
SaveDataMemorySetupResult result{};
if (const int rc = sceSaveDataSetupSaveDataMemory2(&setup, &result); rc != SAVE_DATA_OK) return rc;
if (param == nullptr) return SAVE_DATA_OK;
SaveDataMemorySet2 set{};
set.user_id = user_id;
set.param = param;
return sceSaveDataSetSaveDataMemory2(&set);
}
int APS5_VABI sceSaveDataGetSaveDataMemory(std::int32_t user_id, void* buf, std::size_t buf_size, std::int64_t offset) {
SaveDataMemoryData data{};
data.buf = buf;
data.buf_size = buf_size;
data.offset = static_cast<std::size_t>(offset);
SaveDataMemoryGet2 get{};
get.user_id = user_id;
get.data = &data;
return sceSaveDataGetSaveDataMemory2(&get);
}
int APS5_VABI sceSaveDataSetSaveDataMemory(std::int32_t user_id, void* buf, std::size_t buf_size, std::int64_t offset) {
SaveDataMemoryData data{};
data.buf = buf;
data.buf_size = buf_size;
data.offset = static_cast<std::size_t>(offset);
SaveDataMemorySet2 set{};
set.user_id = user_id;
set.data = &data;
set.data_num = 1;
return sceSaveDataSetSaveDataMemory2(&set);
}
}
+14
View File
@@ -133,3 +133,17 @@ int APS5_VABI sceKernelGetProcessAbiType() {
}
}
extern "C" {
int APS5_VABI __Ux86_64_setcontext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sigreturn() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+119
View File
@@ -0,0 +1,119 @@
#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;
}
+67
View File
@@ -0,0 +1,67 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
extern "C" {
int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize);
int APS5_VABI sceNpWebApiTerminate(int libCtxId);
int APS5_VABI sceNpWebApiCreateHandle(int libCtxId);
int APS5_VABI sceNpWebApiDeleteHandle(int libCtxId, int handleId);
int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterParam, std::size_t filterParamNum);
int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId);
int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, unsigned npServiceLabel, const void* filterParam, std::size_t filterParamNum);
int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId);
}
namespace {
constexpr int InvalidArgument = static_cast<int>(0x80552902u);
constexpr int LibContextNotFound = static_cast<int>(0x80552904u);
constexpr int PushEventFilterNotFound = static_cast<int>(0x8055290Bu);
constexpr int HandleNotFound = static_cast<int>(0x8055290Du);
constexpr int ServicePushEventFilterNotFound = static_cast<int>(0x8055290Eu);
constexpr int SignedInUserNotFound = static_cast<int>(0x80552910u);
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "NpWebApi: %s\n", message);
std::abort();
}
}
}
int main() {
const int context = sceNpWebApiInitialize(1, 0x10000);
Require(context > 0 && context <= 0x7fff, "library context id out of range");
const int other = sceNpWebApiInitialize(1, 0x10000);
Require(other > 0 && other != context, "a second library context must get its own id");
Require(sceNpWebApiCreateHandle(0x7fff + 1) == LibContextNotFound, "handle of an unknown context");
const int handle = sceNpWebApiCreateHandle(context);
Require(handle > 0 && static_cast<unsigned>(handle) < 0xf0000000u, "handle id out of range");
const char filterParam[16] = {};
Require(sceNpWebApiCreatePushEventFilter(context, nullptr, 1) == InvalidArgument, "push event filter without its parameter");
Require(sceNpWebApiCreatePushEventFilter(context, filterParam, 0) == InvalidArgument, "push event filter with no parameters");
const int filter = sceNpWebApiCreatePushEventFilter(context, filterParam, 1);
Require(filter > 0 && filter != handle, "push event filter id");
Require(sceNpWebApiCreateServicePushEventFilter(context, handle, nullptr, 0, filterParam, 1) == InvalidArgument, "service filter without a service name");
Require(sceNpWebApiCreateServicePushEventFilter(0x7fff + 1, handle, "service", 0, filterParam, 1) == LibContextNotFound, "service filter of an unknown context");
Require(sceNpWebApiCreateServicePushEventFilter(other, handle, "service", 0, filterParam, 1) == HandleNotFound, "service filter with another context's handle");
Require(sceNpWebApiCreateServicePushEventFilter(context, handle, "service", 0, filterParam, 1) == SignedInUserNotFound, "service filter without a signed-in user");
Require(sceNpWebApiDeleteServicePushEventFilter(context, 1) == ServicePushEventFilterNotFound, "no service filter exists");
Require(sceNpWebApiDeletePushEventFilter(other, filter) == PushEventFilterNotFound, "filter deleted through another context");
Require(sceNpWebApiDeletePushEventFilter(context, filter) == 0, "delete the push event filter");
Require(sceNpWebApiDeletePushEventFilter(context, filter) == PushEventFilterNotFound, "push event filter deleted twice");
Require(sceNpWebApiDeleteHandle(context, handle) == 0, "delete the handle");
Require(sceNpWebApiDeleteHandle(context, handle) == HandleNotFound, "handle deleted twice");
Require(sceNpWebApiTerminate(context) == 0, "terminate the library context");
Require(sceNpWebApiTerminate(context) == LibContextNotFound, "library context terminated twice");
Require(sceNpWebApiCreateHandle(context) == LibContextNotFound, "handle of a terminated context");
Require(sceNpWebApiTerminate(other) == 0, "terminate the second library context");
return 0;
}
+24
View File
@@ -3,6 +3,7 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <random>
#include <stdexcept>
#include <string>
@@ -10,10 +11,16 @@
extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*);
extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*);
extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*);
extern "C" int APS5_VABI sceSaveDataInitialize(const void*);
extern "C" int APS5_VABI sceSaveDataTerminate();
extern "C" int APS5_VABI sceSaveDataSetupSaveDataMemory(std::int32_t, std::size_t, const SaveDataParam*);
extern "C" int APS5_VABI sceSaveDataSetSaveDataMemory(std::int32_t, void*, std::size_t, std::int64_t);
extern "C" int APS5_VABI sceSaveDataGetSaveDataMemory(std::int32_t, void*, std::size_t, std::int64_t);
namespace {
constexpr int SaveDataErrorNotFound = -2137063416;
constexpr int SaveDataErrorNotInitialized = -2137063423;
int failures = 0;
@@ -73,6 +80,22 @@ void TestDirNameSearchPs4() {
Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws");
}
void TestSaveDataMemoryV1() {
Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == SaveDataErrorNotInitialized, "v1 SetupSaveDataMemory before v1 Initialize");
Check(sceSaveDataInitialize(nullptr) == 0, "v1 Initialize");
Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == 0, "v1 SetupSaveDataMemory");
char written[16] = "settings-v1";
Check(sceSaveDataSetSaveDataMemory(7, written, sizeof(written), 0x100) == 0, "v1 SetSaveDataMemory at an offset");
char read[16] = {};
Check(sceSaveDataGetSaveDataMemory(7, read, sizeof(read), 0x100) == 0, "v1 GetSaveDataMemory");
Check(std::memcmp(read, written, sizeof(read)) == 0, "v1 GetSaveDataMemory returns the bytes v1 set");
std::ifstream slot("_sd_mem/u7/slot0.bin", std::ios::binary);
slot.seekg(0x100);
char stored[16] = {};
Check(static_cast<bool>(slot.read(stored, sizeof(stored))) && std::memcmp(stored, written, sizeof(stored)) == 0, "v1 SetSaveDataMemory stores into slot 0 of the user");
Check(sceSaveDataTerminate() == 0, "Terminate after v1 Initialize");
}
}
int main() {
@@ -83,6 +106,7 @@ int main() {
TestTransferringMountPs4();
TestDirNameSearchPs4();
TestSaveDataMemoryV1();
std::filesystem::current_path(previous);
std::error_code error;
+5
View File
@@ -0,0 +1,5 @@
# libSceNpTrophy technical debt
### Unknown function info
- [sceNpTrophyRegisterContext, sceNpTrophyGetTrophyUnlockState, sceNpTrophyUnlockTrophy](../../../core/libs/prx/libSceNpTrophy/Export.cpp) (libSceNpTrophy, PS4) - the trophy definitions in `TROPCONF.ESFM` are encrypted, so the trophy count is the number of consecutive `TROPnnn.PNG` icons in the title's `/app0/sce_sys/trophy/trophyNN.trp`, and the calls that return names and details (`sceNpTrophyGetGameInfo`, `sceNpTrophyGetGroupInfo`, `sceNpTrophyGetTrophyInfo`) throw. Unlocks are kept per user and service label in `_trophy/trophyNN.<user id>` under the working directory, no platinum trophy is reported (`platinumId` is -1), and a locked trophy's icon fails with `TROPHY_NOT_UNLOCKED`. None of it is checked on a console.
+6
View File
@@ -0,0 +1,6 @@
# libSceNpWebApi technical debt
### Unknown function info
- [sceNpWebApiInitialize, sceNpWebApiTerminate, sceNpWebApiCreateHandle, sceNpWebApiDeleteHandle, sceNpWebApiCreatePushEventFilter, sceNpWebApiDeletePushEventFilter](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - signatures, id ranges (library contexts 1 to 0x7fff, handles and filters below 0xf0000000) and errors (`LIB_CONTEXT_NOT_FOUND`, `HANDLE_NOT_FOUND`, `PUSH_EVENT_FILTER_NOT_FOUND`, `INVALID_ARGUMENT` for a missing filter parameter) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_web_api/np_web_api_internal.cpp); they are not checked on a console. The libHttp context and pool size are kept but not used, since no request is ever sent. Limbo (CUSA01664) creates a context, a handle and filters when its game starts.
- [sceNpWebApiCreateServicePushEventFilter](../../../core/libs/prx/libSceNpWebApi/Export.cpp) (libSceNpWebApi, PS4) - a filter for a PSN service needs the signed-in user, and no user is signed in to PSN, so a valid call returns `SIGNED_IN_USER_NOT_FOUND` after the context and handle checks, as shadPS4 does with its network disabled. shadPS4 also rejects an invalid service label for titles built with SDK 2.00 or later; AnyPS5 has no compiled-SDK version for libraries yet, so such a call returns `SIGNED_IN_USER_NOT_FOUND` instead of `INVALID_ARGUMENT`. `sceNpWebApiDeleteServicePushEventFilter` therefore never finds a filter.