From 43bb7c4aa70cc43b0f9f6cb41d90d71e86d600b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 17 Mar 2026 17:01:24 +0100 Subject: [PATCH 1/3] NpScore --- .gitmodules | 3 + CMakeLists.txt | 2 +- externals/CMakeLists.txt | 3 + externals/cppcodec | 1 + src/core/libraries/np/np_manager.cpp | 13 + src/core/libraries/np/np_manager.h | 12 + src/core/libraries/np/np_score.cpp | 614 +++++++++++++++++++++++++-- src/core/libraries/np/np_score.h | 137 +++--- 8 files changed, 686 insertions(+), 99 deletions(-) create mode 160000 externals/cppcodec diff --git a/.gitmodules b/.gitmodules index 55ae48ea3..6914c9e39 100644 --- a/.gitmodules +++ b/.gitmodules @@ -133,3 +133,6 @@ [submodule "externals/openal-soft"] path = externals/openal-soft url = https://github.com/shadexternals/openal-soft.git +[submodule "externals/cppcodec"] + path = externals/cppcodec + url = https://github.com/tplgy/cppcodec.git diff --git a/CMakeLists.txt b/CMakeLists.txt index cbf373e57..674f3187d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1137,7 +1137,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib Cppcodec) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 41a0f71c7..01fa54c82 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -317,3 +317,6 @@ endif() add_library(Cpp_Httplib INTERFACE) target_include_directories(Cpp_Httplib INTERFACE cpp-httplib/) +# cppcodec +add_library(Cppcodec INTERFACE) +target_include_directories(Cppcodec INTERFACE cppcodec/) diff --git a/externals/cppcodec b/externals/cppcodec new file mode 160000 index 000000000..8019b8b58 --- /dev/null +++ b/externals/cppcodec @@ -0,0 +1 @@ +Subproject commit 8019b8b580f8573c33c50372baec7039dfe5a8ce diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 0ffbb682a..4e7b83975 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -19,6 +19,7 @@ namespace Libraries::Np::NpManager { static bool g_signed_in = false; static s32 g_active_requests = 0; static std::mutex g_request_mutex; +OrbisNpTitleId g_np_title_id = {}; static std::map> g_np_callbacks; static std::mutex g_np_callbacks_mutex; @@ -686,6 +687,17 @@ sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServi return ORBIS_OK; } +s32 PS4_SYSV_ABI sceNpSetNpTitleId(OrbisNpTitleId* title_id, OrbisNpTitleSecret* title_secret) { + if (!title_id || !title_secret) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + LOG_DEBUG(Lib_NpManager, "titleId = {}", title_id->id); + + g_np_title_id = *title_id; + + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId user_id, bool* has_signed_up) { LOG_DEBUG(Lib_NpManager, "called"); @@ -825,6 +837,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("XDncXQIJUSk", "libSceNpManager", 1, "libSceNpManager", sceNpGetOnlineId); LIB_FUNCTION("eQH7nWPcAgc", "libSceNpManager", 1, "libSceNpManager", sceNpGetState); LIB_FUNCTION("VgYczPGB5ss", "libSceNpManager", 1, "libSceNpManager", sceNpGetUserIdByAccountId); + LIB_FUNCTION("Ec63y59l9tw", "libSceNpManager", 1, "libSceNpManager", sceNpSetNpTitleId); LIB_FUNCTION("Oad3rvY-NJQ", "libSceNpManager", 1, "libSceNpManager", sceNpHasSignedUp); LIB_FUNCTION("3Zl8BePTh9Y", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallback); LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib); diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 49250db03..9dce08122 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "common/types.h" #include "core/libraries/np/np_error.h" @@ -90,6 +91,15 @@ struct OrbisNpCreateAsyncRequestParameter { u8 padding[4]; }; +struct OrbisNpTitleId { + char id[33]; + u8 padding[3]; +}; + +struct OrbisNpTitleSecret { + u8 data[128]; +}; + void RegisterNpCallback(std::string key, std::function cb); void DeregisterNpCallback(std::string key); @@ -98,5 +108,7 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); +extern OrbisNpTitleId g_np_title_id; + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpManager diff --git a/src/core/libraries/np/np_score.cpp b/src/core/libraries/np/np_score.cpp index 49bb956b2..c6186d9c8 100644 --- a/src/core/libraries/np/np_score.cpp +++ b/src/core/libraries/np/np_score.cpp @@ -1,13 +1,228 @@ // SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#ifdef _WIN32 +#ifndef NOGDI +#define NOGDI +#endif +#endif + +#include + #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_manager.h" #include "core/libraries/np/np_score.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/np/object_manager.h" +#include "core/libraries/system/userservice.h" +#include "cppcodec/base64_rfc4648.hpp" +#include "externals/cpp-httplib/httplib.h" +#include "nlohmann/json.hpp" namespace Libraries::Np::NpScore { +using json = nlohmann::json; +using base64 = cppcodec::base64_rfc4648; + +const std::string BASE_URL = "http://127.0.0.1:3000"; + +int PackReqId(int libCtxId, int reqId) { + return ((libCtxId & 0xFFFF) << 16) | (reqId & 0xFFFF); +} + +std::pair UnpackReqId(int reqId) { + return {reqId >> 16, reqId & 0xFFFF}; +} + +bool IsReqId(int id) { + return id > (1 << 16); +} + +httplib::Client NewClient() { + httplib::Client client(BASE_URL); + + client.set_default_headers({{"X-NP-TITLE-ID", NpManager::g_np_title_id.id}}); + client.set_bearer_token_auth("BEARER_TOKEN"); // fix when auth manager + + client.set_logger([](const httplib::Request& req, const httplib::Response& res) { + std::cout << "✓ " << req.method << " " << req.path << " -> " << res.status << " (" + << res.body.size() << ") bytes " << res.body << std::endl; + }); + client.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { + std::cerr << "✗ "; + if (req) { + std::cerr << req->method << " " << req->path << " "; + } + std::cerr << "failed: " << httplib::to_string(err); + + // Add specific guidance based on error type + switch (err) { + case httplib::Error::Connection: + std::cerr << " (verify server is running and reachable)"; + break; + case httplib::Error::SSLConnection: + std::cerr << " (check SSL certificate and TLS configuration)"; + break; + case httplib::Error::ConnectionTimeout: + std::cerr << " (increase timeout or check network latency)"; + break; + case httplib::Error::Read: + std::cerr << " (server may have closed connection prematurely)"; + break; + default: + break; + } + std::cerr << std::endl; + }); + + return client; +} + +struct NpScoreRequest { + u32 pcId; + std::future requestFuture; + + void GetRankingByRange(OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankDataA* ranks, u64 ranksLen, + OrbisNpScoreGameInfo* gameInfos, OrbisNpScoreComment* comments, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords) { + requestFuture = std::async(std::launch::async, [=]() { + httplib::Client client = NewClient(); + auto uri = std::format("/score/v1/ranking/{}/by-range?startRank={}&scores={}", + scoreBoardId, startRank, ranksLen); + + auto res = client.Get(uri); + + if (res && res->status == httplib::StatusCode::OK_200) { + auto json = json::parse(res->body); + + auto card = json["card"].get(); + + if (totalRecords) { + *totalRecords = card; + } + + auto json_ranks = json["ranks"]; + auto ranks_obtained = 0; + for (auto r : json_ranks) { + strcpy(ranks[ranks_obtained].onlineId.data, "shadps4"); + ranks[ranks_obtained].pcId = r["id"]["pcId"].get(); + // most probably not correct but need more RE to figure out what are the + // different ranks here + ranks[ranks_obtained].rank1 = r["rank"].get(); + ranks[ranks_obtained].rank2 = r["rank"].get(); + ranks[ranks_obtained].rank3 = r["rank"].get(); + ranks[ranks_obtained].hasGameData = false; + ranks[ranks_obtained].score = r["score"].get(); + Libraries::Rtc::sceRtcGetCurrentTick(&ranks[ranks_obtained].recordTime); + ranks[ranks_obtained].accountId = r["id"]["accountId"].get(); + if (gameInfos && r.contains("gameInfo")) { + std::vector decoded = base64::decode(r["gameInfo"].get()); + gameInfos[ranks_obtained].dataSize = decoded.size(); + if (decoded.size() > sizeof(OrbisNpScoreGameInfo::data)) { + LOG_WARNING(Lib_NpScore, "gameInfo is too long! {} bytes", + decoded.size()); + } else { + std::memcpy(gameInfos[ranks_obtained].data, decoded.data(), + decoded.size()); + } + } + if (comments && r.contains("comment")) { + LOG_WARNING(Lib_NpScore, "score comment returned but not handled"); + } + ranks_obtained++; + } + + if (lastUpdate) { + Libraries::Rtc::sceRtcGetCurrentTick(lastUpdate); + } + + return ranks_obtained; + } + + return -1; + }); + } + + void RecordScore(OrbisNpScoreBoardId boardId, OrbisNpScoreValue score, + OrbisNpScoreComment* comment, OrbisNpScoreGameInfo* gameInfo) { + requestFuture = std::async(std::launch::async, [=, this]() { + httplib::Client client = NewClient(); + auto uri = std::format("/score/v1/ranking/{}", boardId); + json payload; + + payload["pcId"] = this->pcId; + payload["value"] = score; + + if (comment) { + LOG_DEBUG(Lib_NpScore, "score comment {}", comment->comment); + payload["comment"] = base64::encode(comment->comment); + } + + if (gameInfo) { + payload["gameInfo"] = base64::encode(gameInfo->data); + } + + auto res = client.Post(uri, payload.dump(), "application/json"); + + return 0; + }); + } +}; + +using NpScoreRequestManager = + ObjectManager; + +struct NpScoreTitleContext { + u32 serviceLabel; + OrbisNpId npId; + u32 pcId; + NpScoreRequestManager requestManager; + + s32 GetRequest(int reqId, NpScoreRequest** out) { + NpScoreRequest* req = nullptr; + if (auto ret = requestManager.GetObject(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + + return ORBIS_OK; + } + + s32 DeleteRequest(int reqId) { + return requestManager.DeleteObject(reqId); + } +}; + +using NpScoreContextManager = + ObjectManager; + +static NpScoreContextManager ctxManager; + +s32 GetRequest(int requestId, NpScoreRequest** out) { + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpScoreTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + NpScoreRequest* req = nullptr; + if (auto ret = ctx->GetRequest(reqId, &req); ret < 0) { + return ret; + } + + *out = req; + + return ORBIS_OK; +} + int PS4_SYSV_ABI sceNpScoreAbortRequest() { LOG_ERROR(Lib_NpScore, "(STUBBED) called"); return ORBIS_OK; @@ -28,19 +243,46 @@ int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(Libraries::Np::OrbisNpServiceLabel serviceLabel, + OrbisNpId* npId) { + if (!npId) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (serviceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + LOG_INFO(Lib_NpScore, "serviceLabel = {}, npId->data = {}", serviceLabel, npId->handle.data); + + return ctxManager.CreateObject(serviceLabel, *npId); } -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA( + OrbisNpServiceLabel serviceLabel, Libraries::UserService::OrbisUserServiceUserId userId) { + LOG_INFO(Lib_NpScore, "serviceLabel = {}, userId = {}", serviceLabel, userId); + OrbisNpId npId; + auto ret = NpManager::sceNpGetNpId(userId, &npId); + + if (ret < 0) { + return ret; + } + + return sceNpScoreCreateNpTitleCtx(serviceLabel, &npId); } -int PS4_SYSV_ABI sceNpScoreCreateRequest() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreCreateRequest(int libCtxId) { + LOG_INFO(Lib_NpScore, "libCtxId = {}", libCtxId); + + NpScoreTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(libCtxId, &ctx); ret < 0) { + return ret; + } + + auto req = ctx->requestManager.CreateObject(ctx->pcId); + if (req < 0) { + return req; + } + + return PackReqId(libCtxId, req); } int PS4_SYSV_ABI sceNpScoreCreateTitleCtx() { @@ -48,14 +290,23 @@ int PS4_SYSV_ABI sceNpScoreCreateTitleCtx() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(int libCtxId) { + LOG_INFO(Lib_NpScore, "libCtxId = {}", libCtxId); + + return ctxManager.DeleteObject(libCtxId); } -int PS4_SYSV_ABI sceNpScoreDeleteRequest() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreDeleteRequest(int requestId) { + LOG_INFO(Lib_NpScore, "requestId = {:#x}", requestId); + + auto [ctxId, reqId] = UnpackReqId(requestId); + + NpScoreTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + return ctx->DeleteRequest(reqId); } int PS4_SYSV_ABI sceNpScoreGetBoardInfo() { @@ -68,11 +319,67 @@ int PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetFriendsRanking() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync( + int reqId, int scoreBoardId, int includeMe, OrbisNpScoreRankData* rankArray, u64 rankArraySize, + OrbisNpScoreComment* comments, u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, + u64 gameInfosBytes, u64 arrayLen, Libraries::Rtc::OrbisRtcTick* lastUpdate, u64* totalRecords, + void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) reqId = {:#x}, scoreBoardId = {}, includeMe = {}, rankArray = {}, " + "rankArraySize = {}, comments = {}, commentsBytes = {}, gameInfos = {}, " + "gameInfosBytes = {}, arrayLen = {}", + reqId, scoreBoardId, includeMe, fmt::ptr(rankArray), rankArraySize, + fmt::ptr(comments), commentsBytes, fmt::ptr(gameInfos), gameInfosBytes, arrayLen); + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->requestFuture = std::async([=]() { + // fake an empty response + if (totalRecords) { + *totalRecords = 0; + } + if (lastUpdate) { + Libraries::Rtc::sceRtcGetCurrentTick(lastUpdate); + } + + return 0; + }); + return ORBIS_OK; } +int PS4_SYSV_ABI sceNpScoreGetFriendsRanking(int reqId, int scoreBoardId, int includeMe, + OrbisNpScoreRankData* rankArray, u64 rankArraySize, + OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, + u64 arrayLen, Libraries::Rtc::OrbisRtcTick* lastUpdate, + u64* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) reqId = {:#x}, scoreBoardId = {}, includeMe = {}, rankArray = {}, " + "rankArraySize = {}, comments = {}, commentsBytes = {}, gameInfos = {}, " + "gameInfosBytes = {}, arrayLen = {}", + reqId, scoreBoardId, includeMe, fmt::ptr(rankArray), rankArraySize, + fmt::ptr(comments), commentsBytes, fmt::ptr(gameInfos), gameInfosBytes, arrayLen); + + auto ret = sceNpScoreGetFriendsRankingAsync( + reqId, scoreBoardId, includeMe, rankArray, rankArraySize, comments, commentsBytes, + gameInfos, gameInfosBytes, arrayLen, lastUpdate, totalRecords, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? +} + int PS4_SYSV_ABI sceNpScoreGetFriendsRankingA() { LOG_ERROR(Lib_NpScore, "(STUBBED) called"); return ORBIS_OK; @@ -83,11 +390,6 @@ int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSave() { LOG_ERROR(Lib_NpScore, "(STUBBED) called"); return ORBIS_OK; @@ -163,19 +465,110 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByNpId() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreNpIdPcId* accountIds, + u64 accountIdsBytes, OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, + OrbisNpScoreComment* comments, u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, + u64 gameInfosBytes, u64 accountsLen, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR( + Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, accountIds = {}, accountIdsBytes = " + "{}, ranks = {}, " + "comments = {}, gameInfos = {}, accountsLen = {}", + reqId, scoreBoardId, fmt::ptr(accountIds), accountIdsBytes, fmt::ptr(ranks), + fmt::ptr(comments), fmt::ptr(gameInfos), accountsLen); + + if (option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (!accountIds || !ranks) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (accountsLen > 101) { + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + } + if (accountsLen * sizeof(*accountIds) != accountIdsBytes || + accountsLen * sizeof(*ranks) != ranksBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (comments && accountsLen * sizeof(*comments) != commentsBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (gameInfos && accountsLen * sizeof(*gameInfos) != gameInfosBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + + // if sdk version > 5.49 zero all of the provided buffers + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->requestFuture = std::async([=]() { + // fake an empty response + if (totalRecords) { + *totalRecords = 0; + } + if (lastUpdate) { + Libraries::Rtc::sceRtcGetCurrentTick(lastUpdate); + } + + return 0; + }); + return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpId* npIds, u64 npIdsBytes, + OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, + u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 accountsLen, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, npIds = {}, npIdsBytes = " + "{}, ranks = {}, " + "comments = {}, gameInfos = {}, accountsLen = {}", + reqId, scoreBoardId, fmt::ptr(npIds), npIdsBytes, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), accountsLen); + + std::vector npIdsPcIds; + std::ranges::transform(std::span(npIds, accountsLen), std::back_inserter(npIdsPcIds), + [](OrbisNpId npId) -> OrbisNpScoreNpIdPcId { return {npId, 0, {}}; }); + + return sceNpScoreGetRankingByNpIdPcIdAsync( + reqId, scoreBoardId, npIdsPcIds.data(), accountsLen * sizeof(OrbisNpScoreNpIdPcId), ranks, + ranksBytes, comments, commentsBytes, gameInfos, gameInfosBytes, accountsLen, lastUpdate, + totalRecords, option); } -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreNpIdPcId* accountIds, + u64 accountIdsBytes, OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, + OrbisNpScoreComment* comments, u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, + u64 gameInfosBytes, u64 accountsLen, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, accountIds = {}, ranks = {}, " + "comments = {}, gameInfos = {}, accountsLen = {}", + reqId, scoreBoardId, fmt::ptr(accountIds), fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), accountsLen); + + int ret = sceNpScoreGetRankingByNpIdPcIdAsync( + reqId, scoreBoardId, accountIds, accountIdsBytes, ranks, ranksBytes, comments, + commentsBytes, gameInfos, gameInfosBytes, accountsLen, lastUpdate, totalRecords, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? } int PS4_SYSV_ABI sceNpScoreGetRankingByRange() { @@ -188,14 +581,58 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); + + if (ranksLen * sizeof(*ranks) != ranksBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (startRank == 0 || option != nullptr || ranksLen > 100) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + // if sdk version > 1.70 zero all of the provided buffers + if (ranks == nullptr || ranksLen == 0) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (comments) { + if (ranksLen * sizeof(*comments) != commentsBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + } + if (gameInfos) { + if (ranksLen * sizeof(*gameInfos) != gameInfosBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + } + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->GetRankingByRange(scoreBoardId, startRank, ranks, ranksLen, gameInfos, comments, + lastUpdate, totalRecords); + return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { + // at least in 9.00 it's identical A vs non-A + return sceNpScoreGetRankingByRangeAsync(reqId, scoreBoardId, startRank, ranks, ranksBytes, + comments, commentsBytes, gameInfos, gameInfosBytes, + ranksLen, lastUpdate, totalRecords, option); } int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave() { @@ -208,9 +645,27 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSaveAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScorePollAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScorePollAsync(int reqId, int* result) { + LOG_INFO(Lib_NpScore, "reqId = {:#x}", reqId); + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->requestFuture.valid()) { + LOG_ERROR(Lib_NpScore, "request not started"); + return 1; + } + if (req->requestFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + LOG_DEBUG(Lib_NpScore, "request finished"); + if (result) { + *result = req->requestFuture.get(); + } + return 0; + } + + return 1; } int PS4_SYSV_ABI sceNpScoreRecordGameData() { @@ -223,14 +678,45 @@ int PS4_SYSV_ABI sceNpScoreRecordGameDataAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreRecordScore() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreRecordScoreAsync(int reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, OrbisNpScoreComment* comment, + OrbisNpScoreGameInfo* gameInfo, void* unk, + Libraries::Rtc::OrbisRtcTick* date, void* option) { + LOG_ERROR(Lib_NpScore, "reqId = {:#x}, boardId = {}, score = {}", reqId, boardId, score); + + if (option) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->RecordScore(boardId, score, comment, gameInfo); + return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreRecordScoreAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpScoreRecordScore(int reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, OrbisNpScoreComment* comment, + OrbisNpScoreGameInfo* gameInfo, void* unk, + Libraries::Rtc::OrbisRtcTick* date, void* option) { + LOG_ERROR(Lib_NpScore, "reqId = {:#x}, boardId = {}, score = {}", reqId, boardId, score); + + int ret = + sceNpScoreRecordScoreAsync(reqId, boardId, score, comment, gameInfo, unk, date, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? } int PS4_SYSV_ABI sceNpScoreSanitizeComment() { @@ -243,8 +729,25 @@ int PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(int id, int pcId) { + LOG_INFO(Lib_NpScore, "id = {:#x}, pcId = {}", id, pcId); + + if (IsReqId(id)) { + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(id, &req); ret < 0) { + return ret; + } + + req->pcId = pcId; + } else { + NpScoreTitleContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(id, &ctx); ret < 0) { + return ret; + } + + ctx->pcId = pcId; + } + return ORBIS_OK; } @@ -258,8 +761,25 @@ int PS4_SYSV_ABI sceNpScoreSetTimeout() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreWaitAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpScoreWaitAsync(int reqId, int* result) { + LOG_INFO(Lib_NpScore, "reqId = {:#x}", reqId); + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + if (!req->requestFuture.valid()) { + LOG_ERROR(Lib_NpScore, "request not started"); + return 1; + } + + req->requestFuture.wait(); + + LOG_DEBUG(Lib_NpScore, "request finished"); + if (result) { + *result = req->requestFuture.get(); + } return ORBIS_OK; } diff --git a/src/core/libraries/np/np_score.h b/src/core/libraries/np/np_score.h index 01cc25f2f..ee6836717 100644 --- a/src/core/libraries/np/np_score.h +++ b/src/core/libraries/np/np_score.h @@ -4,6 +4,8 @@ #pragma once #include "common/types.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/rtc/rtc.h" namespace Core::Loader { class SymbolsResolver; @@ -11,57 +13,90 @@ class SymbolsResolver; namespace Libraries::Np::NpScore { -int PS4_SYSV_ABI sceNpScoreAbortRequest(); -int PS4_SYSV_ABI sceNpScoreCensorComment(); -int PS4_SYSV_ABI sceNpScoreCensorCommentAsync(); -int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners(); -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(); -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA(); -int PS4_SYSV_ABI sceNpScoreCreateRequest(); -int PS4_SYSV_ABI sceNpScoreCreateTitleCtx(); -int PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(); -int PS4_SYSV_ABI sceNpScoreDeleteRequest(); -int PS4_SYSV_ABI sceNpScoreGetBoardInfo(); -int PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRanking(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingA(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAAsync(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSave(); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSaveAsync(); -int PS4_SYSV_ABI sceNpScoreGetGameData(); -int PS4_SYSV_ABI sceNpScoreGetGameDataAsync(); -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountId(); -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountIdAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountId(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSave(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcId(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave(); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByNpId(); -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId(); -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRange(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave(); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSaveAsync(); -int PS4_SYSV_ABI sceNpScorePollAsync(); -int PS4_SYSV_ABI sceNpScoreRecordGameData(); -int PS4_SYSV_ABI sceNpScoreRecordGameDataAsync(); -int PS4_SYSV_ABI sceNpScoreRecordScore(); -int PS4_SYSV_ABI sceNpScoreRecordScoreAsync(); -int PS4_SYSV_ABI sceNpScoreSanitizeComment(); -int PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync(); -int PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(); -int PS4_SYSV_ABI sceNpScoreSetThreadParam(); -int PS4_SYSV_ABI sceNpScoreSetTimeout(); -int PS4_SYSV_ABI sceNpScoreWaitAsync(); +using OrbisNpScoreBoardId = u32; +using OrbisNpScorePcId = u32; +using OrbisNpScoreRankNumber = u32; +using OrbisNpScoreValue = s64; + +struct OrbisNpScoreNpIdPcId { + Np::OrbisNpId npId; + OrbisNpScorePcId pcId; + u8 pad[4]; +}; + +static_assert(sizeof(OrbisNpScoreNpIdPcId) == 0x2c); + +struct OrbisNpScoreAccountIdPcId { + OrbisNpAccountId accountId; + OrbisNpScorePcId pcId; + u8 pad[4]; +}; + +struct OrbisNpScoreRankDataA { + OrbisNpOnlineId onlineId; + u8 reserved[68]; + OrbisNpScorePcId pcId; + OrbisNpScoreRankNumber rank1; + OrbisNpScoreRankNumber rank2; + OrbisNpScoreRankNumber rank3; + int hasGameData; + u8 pad[4]; + OrbisNpScoreValue score; + Rtc::OrbisRtcTick recordTime; + OrbisNpAccountId accountId; + u8 pad1[8]; +}; + +static_assert(sizeof(OrbisNpScoreRankDataA) == 0x90); + +struct OrbisNpScorePlayerRankDataA { + int hasData; + u8 pad[4]; + OrbisNpScoreRankDataA rankData; +}; + +static_assert(sizeof(OrbisNpScorePlayerRankDataA) == 0x98); + +struct OrbisNpScoreRankData { + OrbisNpOnlineId onlineId; + u8 reserved[52]; + OrbisNpScorePcId pcId; + OrbisNpScoreRankNumber rank1; + OrbisNpScoreRankNumber rank2; + OrbisNpScoreRankNumber rank3; + int hasGameData; + u8 pad[4]; + OrbisNpScoreValue score; + Rtc::OrbisRtcTick recordTime; + OrbisNpAccountId accountId; + u8 pad1[8]; +}; + +static_assert(sizeof(OrbisNpScoreRankData) == 0x80); + +struct OrbisNpScorePlayerRankData { + int hasData; + u8 pad[4]; + OrbisNpScoreRankData rankData; +}; + +static_assert(sizeof(OrbisNpScorePlayerRankData) == 0x88); + +struct OrbisNpScoreComment { + char comment[64]; +}; + +static_assert(sizeof(OrbisNpScoreComment) == 64); + +struct OrbisNpScoreGameInfo { + u64 dataSize; + u8 data[189]; + u8 pad[3]; +}; + +static_assert(sizeof(OrbisNpScoreGameInfo) == 200); + +int PS4_SYSV_ABI sceNpScoreWaitAsync(int reqId, int* result); void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpScore \ No newline at end of file From 8d908807e94e4325d985c2341a193b216b41d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Fri, 27 Mar 2026 22:54:21 +0100 Subject: [PATCH 2/3] Correct NpScoreRankData structures --- src/core/libraries/np/np_score.cpp | 126 ++++++++++++++++++++++++----- src/core/libraries/np/np_score.h | 69 ++++++++-------- 2 files changed, 140 insertions(+), 55 deletions(-) diff --git a/src/core/libraries/np/np_score.cpp b/src/core/libraries/np/np_score.cpp index c6186d9c8..821220a92 100644 --- a/src/core/libraries/np/np_score.cpp +++ b/src/core/libraries/np/np_score.cpp @@ -85,8 +85,9 @@ struct NpScoreRequest { u32 pcId; std::future requestFuture; + template void GetRankingByRange(OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, - OrbisNpScoreRankDataA* ranks, u64 ranksLen, + T* ranks, u64 ranksLen, OrbisNpScoreGameInfo* gameInfos, OrbisNpScoreComment* comments, Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords) { requestFuture = std::async(std::launch::async, [=]() { @@ -108,17 +109,23 @@ struct NpScoreRequest { auto json_ranks = json["ranks"]; auto ranks_obtained = 0; for (auto r : json_ranks) { - strcpy(ranks[ranks_obtained].onlineId.data, "shadps4"); + if constexpr (std::is_same_v) { + strcpy(ranks[ranks_obtained].npId.handle.data, "shadps4"); + } else if constexpr (std::is_same_v) { + strcpy(ranks[ranks_obtained].onlineId.data, "shadps4"); + ranks[ranks_obtained].accountId = r["id"]["accountId"].get(); + } else { + static_assert(false, "unsupported type"); + } ranks[ranks_obtained].pcId = r["id"]["pcId"].get(); // most probably not correct but need more RE to figure out what are the // different ranks here - ranks[ranks_obtained].rank1 = r["rank"].get(); - ranks[ranks_obtained].rank2 = r["rank"].get(); - ranks[ranks_obtained].rank3 = r["rank"].get(); + ranks[ranks_obtained].serialRank = r["rank"].get(); + ranks[ranks_obtained].rank = r["rank"].get(); + ranks[ranks_obtained].highestRank = r["rank"].get(); ranks[ranks_obtained].hasGameData = false; ranks[ranks_obtained].score = r["score"].get(); Libraries::Rtc::sceRtcGetCurrentTick(&ranks[ranks_obtained].recordTime); - ranks[ranks_obtained].accountId = r["id"]["accountId"].get(); if (gameInfos && r.contains("gameInfo")) { std::vector decoded = base64::decode(r["gameInfo"].get()); gameInfos[ranks_obtained].dataSize = decoded.size(); @@ -571,19 +578,9 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId( return -1; //? } -int PS4_SYSV_ABI sceNpScoreGetRankingByRange() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, - OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreRankData* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { LOG_ERROR(Lib_NpScore, @@ -624,15 +621,102 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( return ORBIS_OK; } +int PS4_SYSV_ABI sceNpScoreGetRankingByRange( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankData* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); + + int ret = sceNpScoreGetRankingByRangeAsync( + reqId, scoreBoardId, startRank, ranks, ranksBytes, comments, + commentsBytes, gameInfos, gameInfosBytes, ranksLen, lastUpdate, totalRecords, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? +} + int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { - // at least in 9.00 it's identical A vs non-A - return sceNpScoreGetRankingByRangeAsync(reqId, scoreBoardId, startRank, ranks, ranksBytes, - comments, commentsBytes, gameInfos, gameInfosBytes, - ranksLen, lastUpdate, totalRecords, option); + + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); + + if (ranksLen * sizeof(*ranks) != ranksBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (startRank == 0 || option != nullptr || ranksLen > 100) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + // if sdk version > 1.70 zero all of the provided buffers + if (ranks == nullptr || ranksLen == 0) { + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (comments) { + if (ranksLen * sizeof(*comments) != commentsBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + } + if (gameInfos) { + if (ranksLen * sizeof(*gameInfos) != gameInfosBytes) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + } + + NpScoreRequest* req = nullptr; + if (auto ret = GetRequest(reqId, &req); ret < 0) { + return ret; + } + + req->GetRankingByRange(scoreBoardId, startRank, ranks, ranksLen, gameInfos, comments, + lastUpdate, totalRecords); + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( + int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, + Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); + + int ret = sceNpScoreGetRankingByRangeAAsync( + reqId, scoreBoardId, startRank, ranks, ranksBytes, comments, + commentsBytes, gameInfos, gameInfosBytes, ranksLen, lastUpdate, totalRecords, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? } int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave() { diff --git a/src/core/libraries/np/np_score.h b/src/core/libraries/np/np_score.h index ee6836717..ceea386ab 100644 --- a/src/core/libraries/np/np_score.h +++ b/src/core/libraries/np/np_score.h @@ -32,48 +32,41 @@ struct OrbisNpScoreAccountIdPcId { u8 pad[4]; }; -struct OrbisNpScoreRankDataA { - OrbisNpOnlineId onlineId; - u8 reserved[68]; - OrbisNpScorePcId pcId; - OrbisNpScoreRankNumber rank1; - OrbisNpScoreRankNumber rank2; - OrbisNpScoreRankNumber rank3; - int hasGameData; - u8 pad[4]; - OrbisNpScoreValue score; - Rtc::OrbisRtcTick recordTime; - OrbisNpAccountId accountId; - u8 pad1[8]; -}; - -static_assert(sizeof(OrbisNpScoreRankDataA) == 0x90); - -struct OrbisNpScorePlayerRankDataA { - int hasData; - u8 pad[4]; - OrbisNpScoreRankDataA rankData; -}; - -static_assert(sizeof(OrbisNpScorePlayerRankDataA) == 0x98); - struct OrbisNpScoreRankData { - OrbisNpOnlineId onlineId; - u8 reserved[52]; + OrbisNpId npId; + u8 reserved[49]; + u8 pad0[3]; OrbisNpScorePcId pcId; - OrbisNpScoreRankNumber rank1; - OrbisNpScoreRankNumber rank2; - OrbisNpScoreRankNumber rank3; - int hasGameData; - u8 pad[4]; + OrbisNpScoreRankNumber serialRank; + OrbisNpScoreRankNumber rank; + OrbisNpScoreRankNumber highestRank; OrbisNpScoreValue score; + int hasGameData; + u8 pad1[4]; Rtc::OrbisRtcTick recordTime; - OrbisNpAccountId accountId; - u8 pad1[8]; }; static_assert(sizeof(OrbisNpScoreRankData) == 0x80); +struct OrbisNpScoreRankDataA { + OrbisNpOnlineId onlineId; + u8 reserved0[16]; + u8 reserved[49]; + u8 pad0[3]; + OrbisNpScorePcId pcId; + OrbisNpScoreRankNumber serialRank; + OrbisNpScoreRankNumber rank; + OrbisNpScoreRankNumber highestRank; + int hasGameData; + u8 pad1[4]; + OrbisNpScoreValue score; + Rtc::OrbisRtcTick recordTime; + OrbisNpAccountId accountId; + u8 pad2[8]; +}; + +static_assert(sizeof(OrbisNpScoreRankDataA) == 0x90); + struct OrbisNpScorePlayerRankData { int hasData; u8 pad[4]; @@ -82,6 +75,14 @@ struct OrbisNpScorePlayerRankData { static_assert(sizeof(OrbisNpScorePlayerRankData) == 0x88); +struct OrbisNpScorePlayerRankDataA { + int hasData; + u8 pad[4]; + OrbisNpScoreRankDataA rankData; +}; + +static_assert(sizeof(OrbisNpScorePlayerRankDataA) == 0x98); + struct OrbisNpScoreComment { char comment[64]; }; From ff355f8dd9f9364582437dcb6327f710f51f8b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Fri, 27 Mar 2026 23:00:13 +0100 Subject: [PATCH 3/3] Define sceNpScoreGetRankingByNpId --- src/core/libraries/np/np_score.cpp | 93 ++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/src/core/libraries/np/np_score.cpp b/src/core/libraries/np/np_score.cpp index 821220a92..ca7469bef 100644 --- a/src/core/libraries/np/np_score.cpp +++ b/src/core/libraries/np/np_score.cpp @@ -87,9 +87,9 @@ struct NpScoreRequest { template void GetRankingByRange(OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, - T* ranks, u64 ranksLen, - OrbisNpScoreGameInfo* gameInfos, OrbisNpScoreComment* comments, - Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords) { + T* ranks, u64 ranksLen, OrbisNpScoreGameInfo* gameInfos, + OrbisNpScoreComment* comments, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords) { requestFuture = std::async(std::launch::async, [=]() { httplib::Client client = NewClient(); auto uri = std::format("/score/v1/ranking/{}/by-range?startRank={}&scores={}", @@ -467,11 +467,6 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByNpId() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync( int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreNpIdPcId* accountIds, u64 accountIdsBytes, OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, @@ -550,6 +545,36 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync( totalRecords, option); } +int PS4_SYSV_ABI sceNpScoreGetRankingByNpId(int reqId, OrbisNpScoreBoardId scoreBoardId, + OrbisNpId* npIds, u64 npIdsBytes, + OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, + OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, + u64 accountsLen, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, npIds = {}, npIdsBytes = " + "{}, ranks = {}, " + "comments = {}, gameInfos = {}, accountsLen = {}", + reqId, scoreBoardId, fmt::ptr(npIds), npIdsBytes, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), accountsLen); + + int ret = sceNpScoreGetRankingByNpIdAsync( + reqId, scoreBoardId, npIds, npIdsBytes, ranks, ranksBytes, comments, commentsBytes, + gameInfos, gameInfosBytes, accountsLen, lastUpdate, totalRecords, option); + + if (ret < 0) { + return ret; + } + + int result = 0; + if (sceNpScoreWaitAsync(reqId, &result) == 0) { + return result; + } + + return -1; //? +} + int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId( int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreNpIdPcId* accountIds, u64 accountIdsBytes, OrbisNpScorePlayerRankData* ranks, u64 ranksBytes, @@ -621,20 +646,22 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByRange( - int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, - OrbisNpScoreRankData* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, - OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, - Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { +int PS4_SYSV_ABI sceNpScoreGetRankingByRange(int reqId, OrbisNpScoreBoardId scoreBoardId, + OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankData* ranks, u64 ranksBytes, + OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, + u64 ranksLen, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords, void* option) { LOG_ERROR(Lib_NpScore, - "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " - "comments = {}, gameInfos = {}, ranksLen = {}", - reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), - fmt::ptr(gameInfos), ranksLen); + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); - int ret = sceNpScoreGetRankingByRangeAsync( - reqId, scoreBoardId, startRank, ranks, ranksBytes, comments, - commentsBytes, gameInfos, gameInfosBytes, ranksLen, lastUpdate, totalRecords, option); + int ret = sceNpScoreGetRankingByRangeAsync(reqId, scoreBoardId, startRank, ranks, ranksBytes, + comments, commentsBytes, gameInfos, gameInfosBytes, + ranksLen, lastUpdate, totalRecords, option); if (ret < 0) { return ret; @@ -692,20 +719,22 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( return ORBIS_OK; } -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( - int reqId, OrbisNpScoreBoardId scoreBoardId, OrbisNpScoreRankNumber startRank, - OrbisNpScoreRankDataA* ranks, u64 ranksBytes, OrbisNpScoreComment* comments, u64 commentsBytes, - OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, u64 ranksLen, - Rtc::OrbisRtcTick* lastUpdate, OrbisNpScoreRankNumber* totalRecords, void* option) { +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA(int reqId, OrbisNpScoreBoardId scoreBoardId, + OrbisNpScoreRankNumber startRank, + OrbisNpScoreRankDataA* ranks, u64 ranksBytes, + OrbisNpScoreComment* comments, u64 commentsBytes, + OrbisNpScoreGameInfo* gameInfos, u64 gameInfosBytes, + u64 ranksLen, Rtc::OrbisRtcTick* lastUpdate, + OrbisNpScoreRankNumber* totalRecords, void* option) { LOG_ERROR(Lib_NpScore, - "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " - "comments = {}, gameInfos = {}, ranksLen = {}", - reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), - fmt::ptr(gameInfos), ranksLen); + "(STUBBED) called, reqId = {:#x}, scoreBoardId = {}, startRank = {}, ranks = {}, " + "comments = {}, gameInfos = {}, ranksLen = {}", + reqId, scoreBoardId, startRank, fmt::ptr(ranks), fmt::ptr(comments), + fmt::ptr(gameInfos), ranksLen); - int ret = sceNpScoreGetRankingByRangeAAsync( - reqId, scoreBoardId, startRank, ranks, ranksBytes, comments, - commentsBytes, gameInfos, gameInfosBytes, ranksLen, lastUpdate, totalRecords, option); + int ret = sceNpScoreGetRankingByRangeAAsync(reqId, scoreBoardId, startRank, ranks, ranksBytes, + comments, commentsBytes, gameInfos, gameInfosBytes, + ranksLen, lastUpdate, totalRecords, option); if (ret < 0) { return ret;