diff --git a/.gitmodules b/.gitmodules index 5211eee3b..fa1d35c4e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -141,3 +141,6 @@ url = https://github.com/gabime/spdlog.git shallow = true branch = v2.x +[submodule "externals/protobuf"] + path = externals/protobuf + url = https://github.com/shadexternals/protobuf.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6524222aa..e10c191cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -606,8 +606,6 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_manager.h src/core/libraries/np/np_matching2.cpp src/core/libraries/np/np_matching2.h - src/core/libraries/np/np_score.cpp - src/core/libraries/np/np_score.h src/core/libraries/np/np_trophy.cpp src/core/libraries/np/np_trophy.h src/core/libraries/np/np_tus.cpp @@ -616,7 +614,6 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/trophy_ui.h src/core/libraries/np/np_web_api.cpp src/core/libraries/np/np_web_api.h - src/core/libraries/np/np_web_api_error.h src/core/libraries/np/np_web_api_internal.cpp src/core/libraries/np/np_web_api_internal.h src/core/libraries/np/np_web_api2.cpp @@ -634,6 +631,11 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_partner.cpp src/core/libraries/np/np_partner.h src/core/libraries/np/object_manager.h + src/core/libraries/np/np_score/np_score.cpp + src/core/libraries/np/np_score/np_score.h + src/core/libraries/np/np_score/np_score_ctx.h + src/core/libraries/np/np_handler.cpp + src/core/libraries/np/np_handler.h ) set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp @@ -1128,6 +1130,28 @@ set(EMULATOR src/emulator.cpp src/sdl_window.cpp ) +# shadNet protobuf generation +set(SHADNET_PROTO_OUT "${CMAKE_CURRENT_BINARY_DIR}/shadnet_proto_gen") +file(MAKE_DIRECTORY "${SHADNET_PROTO_OUT}") +add_custom_command( + OUTPUT + "${SHADNET_PROTO_OUT}/shadnet.pb.cc" + "${SHADNET_PROTO_OUT}/shadnet.pb.h" + COMMAND protobuf::protoc + "--proto_path=${CMAKE_CURRENT_SOURCE_DIR}/src/shadnet" + "--cpp_out=${SHADNET_PROTO_OUT}" + "${CMAKE_CURRENT_SOURCE_DIR}/src/shadnet/shadnet.proto" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/shadnet/shadnet.proto" + COMMENT "Generating shadnet protobuf sources" + VERBATIM +) + +set(SHADNET src/shadnet/client.cpp + src/shadnet/client.h + "${SHADNET_PROTO_OUT}/shadnet.pb.cc" +) + if(NOT ENABLE_TESTS) add_executable(shadps4 @@ -1139,6 +1163,7 @@ add_executable(shadps4 ${SHADER_RECOMPILER} ${VIDEO_CORE} ${EMULATOR} + ${SHADNET} src/main.cpp src/emulator.cpp src/emulator.h @@ -1151,6 +1176,8 @@ 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 ImGuiFileDialog gcn half::half ZLIB::ZLIB PNG::PNG minimp3) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml) target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib spdlog::spdlog) +target_link_libraries(shadps4 PRIVATE libprotobuf) +target_include_directories(shadps4 PRIVATE "${SHADNET_PROTO_OUT}") if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") target_link_libraries(shadps4 PRIVATE "/usr/lib/libusb.so") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index ea2772dd7..621f5a1d5 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -331,3 +331,14 @@ endif() add_library(Cpp_Httplib INTERFACE) target_include_directories(Cpp_Httplib INTERFACE cpp-httplib/) +# protobuf +set(protobuf_BUILD_TESTS OFF CACHE INTERNAL "") +set(protobuf_BUILD_EXAMPLES OFF CACHE INTERNAL "") +set(protobuf_BUILD_PROTOC_BINARIES ON CACHE INTERNAL "") +set(protobuf_INSTALL OFF CACHE INTERNAL "") +set(protobuf_WITH_ZLIB OFF CACHE INTERNAL "") +set(protobuf_BUILD_SHARED_LIBS OFF CACHE INTERNAL "") +if(MSVC) +set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "" FORCE) +endif() +add_subdirectory(protobuf EXCLUDE_FROM_ALL) \ No newline at end of file diff --git a/externals/protobuf b/externals/protobuf new file mode 160000 index 000000000..109fde41d --- /dev/null +++ b/externals/protobuf @@ -0,0 +1 @@ +Subproject commit 109fde41db66fdf24763a7a429d1e0dc80459f1d diff --git a/src/common/elf_info.h b/src/common/elf_info.h index 661f911d8..4ffe2b353 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -95,6 +95,7 @@ public: static constexpr u32 FW_70 = 0x7000000; static constexpr u32 FW_75 = 0x7500000; static constexpr u32 FW_80 = 0x8000000; + static constexpr u32 FW_90 = 0x9000000; static constexpr u32 FW_115 = 0x11500000; static ElfInfo& Instance() { diff --git a/src/common/logging/classes.h b/src/common/logging/classes.h index eab6dfe27..aa83bd04f 100644 --- a/src/common/logging/classes.h +++ b/src/common/logging/classes.h @@ -104,6 +104,8 @@ constexpr auto Lib_WebBrowserDialog = "Lib.WebBrowserDialog"; ///< The Lib constexpr auto Lib_Zlib = "Lib.Zlib"; ///< The LibSceZlib implementation. constexpr auto Loader = "Loader"; ///< ROM loader constexpr auto Log = "Log"; ///< Messages about the log system itself +constexpr auto NpHandler = "NpHandler"; ///< NpHandler shadNet manager +constexpr auto ShadNet = "ShadNet"; ///< shadNet binary protocol client constexpr auto Render = "Render"; ///< Video Core constexpr auto Render_Recompiler = "Render.Recompiler"; ///< Shader recompiler constexpr auto Render_Vulkan = "Render.Vulkan"; ///< Vulkan backend diff --git a/src/common/logging/log.cpp b/src/common/logging/log.cpp index 511eda668..f4dab79f5 100644 --- a/src/common/logging/log.cpp +++ b/src/common/logging/log.cpp @@ -121,6 +121,8 @@ std::unordered_map> ALL_LOGGER {Class::Render_Recompiler, nullptr}, {Class::Render_Vulkan, nullptr}, {Class::Tty, nullptr}, + {Class::NpHandler, nullptr}, + {Class::ShadNet, nullptr}, }; template diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index d207126bc..78e580e81 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -39,7 +39,7 @@ #include "core/libraries/np/np_partner.h" #include "core/libraries/np/np_party.h" #include "core/libraries/np/np_profile_dialog/np_profile_dialog.h" -#include "core/libraries/np/np_score.h" +#include "core/libraries/np/np_score/np_score.h" #include "core/libraries/np/np_sns_facebook_dialog.h" #include "core/libraries/np/np_trophy.h" #include "core/libraries/np/np_tus.h" diff --git a/src/core/libraries/np/np_auth.cpp b/src/core/libraries/np/np_auth.cpp index fa6752488..f03f4198c 100644 --- a/src/core/libraries/np/np_auth.cpp +++ b/src/core/libraries/np/np_auth.cpp @@ -7,7 +7,6 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_auth.h" -#include "core/libraries/np/np_auth_error.h" #include "core/libraries/np/np_error.h" #include "core/libraries/system/userservice.h" diff --git a/src/core/libraries/np/np_auth_error.h b/src/core/libraries/np/np_auth_error.h deleted file mode 100644 index bc08bffe6..000000000 --- a/src/core/libraries/np/np_auth_error.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/error_codes.h" - -constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT = 0x80550301; -constexpr int ORBIS_NP_AUTH_ERROR_INVALID_SIZE = 0x80550302; -constexpr int ORBIS_NP_AUTH_ERROR_ABORTED = 0x80550304; -constexpr int ORBIS_NP_AUTH_ERROR_REQUEST_MAX = 0x80550305; -constexpr int ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND = 0x80550306; -constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ID = 0x80550307; \ No newline at end of file diff --git a/src/core/libraries/np/np_common.cpp b/src/core/libraries/np/np_common.cpp index ce0c3799a..6fcf7ef25 100644 --- a/src/core/libraries/np/np_common.cpp +++ b/src/core/libraries/np/np_common.cpp @@ -1,11 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_common.h" -#include "core/libraries/np/np_common_error.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_types.h" diff --git a/src/core/libraries/np/np_common_error.h b/src/core/libraries/np/np_common_error.h deleted file mode 100644 index 1fcef1ece..000000000 --- a/src/core/libraries/np/np_common_error.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/error_codes.h" - -constexpr int ORBIS_NP_UTIL_ERROR_NOT_MATCH = 0x80550609; \ No newline at end of file diff --git a/src/core/libraries/np/np_error.h b/src/core/libraries/np/np_error.h index 518344ba3..9407d8af1 100644 --- a/src/core/libraries/np/np_error.h +++ b/src/core/libraries/np/np_error.h @@ -1,24 +1,676 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "core/libraries/error_codes.h" -// For error codes shared between multiple Np libraries. +// Base NP errors (0x80550001 – 0x8055001F) +constexpr int ORBIS_NP_ERROR_ALREADY_INITIALIZED = 0x80550001; +constexpr int ORBIS_NP_ERROR_NOT_INITIALIZED = 0x80550002; constexpr int ORBIS_NP_ERROR_INVALID_ARGUMENT = 0x80550003; +constexpr int ORBIS_NP_ERROR_UNKNOWN_PLATFORM_TYPE = 0x80550004; +constexpr int ORBIS_NP_ERROR_OUT_OF_MEMORY = 0x80550005; constexpr int ORBIS_NP_ERROR_SIGNED_OUT = 0x80550006; constexpr int ORBIS_NP_ERROR_USER_NOT_FOUND = 0x80550007; +constexpr int ORBIS_NP_ERROR_CALLBACK_ALREADY_REGISTERED = 0x80550008; +constexpr int ORBIS_NP_ERROR_CALLBACK_NOT_REGISTERED = 0x80550009; +constexpr int ORBIS_NP_ERROR_NOT_SIGNED_UP = 0x8055000A; +constexpr int ORBIS_NP_ERROR_AGE_RESTRICTION = 0x8055000B; +constexpr int ORBIS_NP_ERROR_LOGOUT = 0x8055000C; +constexpr int ORBIS_NP_ERROR_LATEST_SYSTEM_SOFTWARE_EXIST = 0x8055000D; +constexpr int ORBIS_NP_ERROR_LATEST_SYSTEM_SOFTWARE_EXIST_FOR_TITLE = 0x8055000E; +constexpr int ORBIS_NP_ERROR_LATEST_PATCH_PKG_EXIST = 0x8055000F; +constexpr int ORBIS_NP_ERROR_LATEST_PATCH_PKG_DOWNLOADED = 0x80550010; constexpr int ORBIS_NP_ERROR_INVALID_SIZE = 0x80550011; constexpr int ORBIS_NP_ERROR_ABORTED = 0x80550012; constexpr int ORBIS_NP_ERROR_REQUEST_MAX = 0x80550013; constexpr int ORBIS_NP_ERROR_REQUEST_NOT_FOUND = 0x80550014; constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015; +constexpr int ORBIS_NP_ERROR_NP_TITLE_DAT_NOT_FOUND = 0x80550016; +constexpr int ORBIS_NP_ERROR_INCONSISTENT_NP_TITLE_ID = 0x80550017; +constexpr int ORBIS_NP_ERROR_PATCH_NOT_CHECKED = 0x80550018; +constexpr int ORBIS_NP_ERROR_TITLE_IS_BANNED = 0x80550019; +constexpr int ORBIS_NP_ERROR_TIMEOUT = 0x8055001A; +constexpr int ORBIS_NP_ERROR_TITLE_ID_IN_PARAM_SFO_NOT_MATCHED_TO_NP_TITLE_ID = 0x8055001B; +constexpr int ORBIS_NP_ERROR_TITLE_ID_IN_PARAM_SFO_NOT_EXIST = 0x8055001C; +constexpr int ORBIS_NP_ERROR_CALLBACK_MAX = 0x8055001D; +constexpr int ORBIS_NP_ERROR_INVALID_NP_TITLE_ID = 0x8055001E; +constexpr int ORBIS_NP_ERROR_ONLINE_ID_CHANGED = 0x8055001F; +// NP Util (0x80550601 – 0x8055060e) +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_ARGUMENT = 0x80550601; +constexpr int ORBIS_NP_UTIL_ERROR_INSUFFICIENT = 0x80550602; +constexpr int ORBIS_NP_UTIL_ERROR_PARSER_FAILED = 0x80550603; +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_PROTOCOL_ID = 0x80550604; +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_NP_ID = 0x80550605; +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_NP_ENV = 0x80550606; +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_CHARACTER = 0x80550608; +constexpr int ORBIS_NP_UTIL_ERROR_NOT_MATCH = 0x80550609; +constexpr int ORBIS_NP_UTIL_ERROR_INVALID_TITLEID = 0x8055060A; +constexpr int ORBIS_NP_UTIL_ERROR_UNKNOWN = 0x8055060E; + +// NP Auth errors (0x80550300 – 0x8055040F) +constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT = 0x80550301; +constexpr int ORBIS_NP_AUTH_ERROR_INVALID_SIZE = 0x80550302; +constexpr int ORBIS_NP_AUTH_ERROR_OUT_OF_MEMORY = 0x80550303; +constexpr int ORBIS_NP_AUTH_ERROR_ABORTED = 0x80550304; +constexpr int ORBIS_NP_AUTH_ERROR_REQUEST_MAX = 0x80550305; +constexpr int ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND = 0x80550306; +constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ID = 0x80550307; +constexpr int ORBIS_NP_AUTH_ERROR_NO_TOKEN_RECEIVED = 0x80550308; +constexpr int ORBIS_NP_AUTH_ERROR_SERVICE_END = 0x80550400; +constexpr int ORBIS_NP_AUTH_ERROR_SERVICE_DOWN = 0x80550401; +constexpr int ORBIS_NP_AUTH_ERROR_SERVICE_BUSY = 0x80550402; +constexpr int ORBIS_NP_AUTH_ERROR_SERVER_MAINTENANCE = 0x80550403; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_DATA_LENGTH = 0x80550410; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_USER_AGENT = 0x80550411; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_VERSION = 0x80550412; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_SERVICE_ID = 0x80550420; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_CREDENTIAL = 0x80550421; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_ENTITLEMENT_ID = 0x80550422; +constexpr int ORBIS_NP_AUTH_ERROR_S_INVALID_CONSUMED_COUNT = 0x80550423; +constexpr int ORBIS_NP_AUTH_ERROR_INVALID_CONSOLE_ID = 0x80550424; +constexpr int ORBIS_NP_AUTH_ERROR_CONSOLE_ID_SUSPENDED = 0x80550427; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_CLOSED = 0x80550430; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_SUSPENDED = 0x80550431; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_EULA = 0x80550432; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT1 = 0x80550440; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT2 = 0x80550441; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT3 = 0x80550442; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT4 = 0x80550443; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT5 = 0x80550444; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT6 = 0x80550445; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT7 = 0x80550446; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT8 = 0x80550447; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT9 = 0x80550448; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT10 = 0x80550449; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT11 = 0x8055044A; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT12 = 0x8055044B; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT13 = 0x8055044C; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT14 = 0x8055044D; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT15 = 0x8055044E; +constexpr int ORBIS_NP_AUTH_ERROR_ACCOUNT_RENEW_ACCOUNT16 = 0x8055044F; +constexpr int ORBIS_NP_AUTH_ERROR_SUB_ACCOUNT_RENEW_EULA = 0x8055044F; +constexpr int ORBIS_NP_AUTH_ERROR_UNKNOWN = 0x80550480; + +// NP Community / Score client errors (0x80550700 – 0x8055071D) +constexpr int ORBIS_NP_COMMUNITY_ERROR_ALREADY_INITIALIZED = 0x80550701; +constexpr int ORBIS_NP_COMMUNITY_ERROR_NOT_INITIALIZED = 0x80550702; +constexpr int ORBIS_NP_COMMUNITY_ERROR_OUT_OF_MEMORY = 0x80550703; constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = 0x80550704; +constexpr int ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN = 0x80550705; constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = 0x80550706; -constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070c; -constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070e; +constexpr int ORBIS_NP_COMMUNITY_ERROR_ABORTED = 0x80550707; +constexpr int ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE = 0x80550708; +constexpr int ORBIS_NP_COMMUNITY_ERROR_BODY_TOO_LARGE = 0x80550709; +constexpr int ORBIS_NP_COMMUNITY_ERROR_HTTP_SERVER = 0x8055070A; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_SIGNATURE = 0x8055070B; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070C; +constexpr int ORBIS_NP_COMMUNITY_ERROR_UNKNOWN_TYPE = 0x8055070D; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070E; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ONLINE_ID = 0x8055070F; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_TYPE = 0x80550711; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TRANSACTION_ALREADY_END = 0x80550712; +constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_PARTITION = 0x80550713; constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = 0x80550714; +constexpr int ORBIS_NP_COMMUNITY_ERROR_CLIENT_HANDLE_ALREADY_EXISTS = 0x80550715; +constexpr int ORBIS_NP_COMMUNITY_ERROR_NO_RESOURCE = 0x80550716; +constexpr int ORBIS_NP_COMMUNITY_ERROR_REQUEST_BEFORE_END = 0x80550717; constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = 0x80550718; -constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719; \ No newline at end of file +constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719; +constexpr int ORBIS_NP_COMMUNITY_ERROR_SCORE_INVALID_SAVEDATA_OWNER = 0x8055071A; +constexpr int ORBIS_NP_COMMUNITY_ERROR_TUS_INVALID_SAVEDATA_OWNER = 0x8055071B; +constexpr int ORBIS_NP_COMMUNITY_ERROR_GHOST_SERVER_RETURN_INVALID_STATUS_CODE = 0x8055071C; +constexpr int ORBIS_NP_COMMUNITY_ERROR_UBS_ONLINE_ID_IN_XML_CREATED_PAST_IS_DIFFERENT_FROM_CURRENT = + 0x8055071D; + +// NP Community / Score server errors (0x80550800 – 0x805508AB) +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_BAD_REQUEST = 0x80550801; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_TICKET = 0x80550802; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_SIGNATURE = 0x80550803; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_NPID = 0x80550805; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_FORBIDDEN = 0x80550806; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INTERNAL_SERVER_ERROR = 0x80550807; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_VERSION_NOT_SUPPORTED = 0x80550808; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_SERVICE_UNAVAILABLE = 0x80550809; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_PLAYER_BANNED = 0x8055080A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_CENSORED = 0x8055080B; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_RECORD_FORBIDDEN = 0x8055080C; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_USER_PROFILE_NOT_FOUND = 0x8055080D; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UPLOADER_DATA_NOT_FOUND = 0x8055080E; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_QUOTA_MASTER_NOT_FOUND = 0x8055080F; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_TITLE_NOT_FOUND = 0x80550810; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_BLACKLISTED_USER_ID = 0x80550811; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_GAME_RANKING_NOT_FOUND = 0x80550812; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_STORE_NOT_FOUND = 0x80550814; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NOT_BEST_SCORE = 0x80550815; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_LATEST_UPDATE_NOT_FOUND = 0x80550816; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_BOARD_MASTER_NOT_FOUND = 0x80550817; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_GAME_DATA_MASTER_NOT_FOUND = 0x80550818; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ANTICHEAT_DATA = 0x80550819; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_LARGE_DATA = 0x8055081A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_USER_NPID = 0x8055081B; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ENVIRONMENT = 0x8055081D; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ONLINE_NAME_CHARACTER = 0x8055081F; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ONLINE_NAME_LENGTH = 0x80550820; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ABOUT_ME_CHARACTER = 0x80550821; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_ABOUT_ME_LENGTH = 0x80550822; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_SCORE = 0x80550823; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_OVER_THE_RANKING_LIMIT = 0x80550824; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_FAIL_TO_CREATE_SIGNATURE = 0x80550826; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_MASTER_INFO_NOT_FOUND = 0x80550827; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_OVER_THE_GAME_DATA_LIMIT = 0x80550828; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_SELF_DATA_NOT_FOUND = 0x8055082A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_USER_NOT_ASSIGNED = 0x8055082B; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_GAME_DATA_ALREADY_EXISTS = 0x8055082C; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_MANY_RESULTS = 0x8055082D; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NOT_RECORDABLE_VERSION = 0x8055082E; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_USER_STORAGE_TITLE_MASTER_NOT_FOUND = 0x80550848; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_VIRTUAL_USER = 0x80550849; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_USER_STORAGE_DATA_NOT_FOUND = 0x8055084A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NON_PLUS_MEMBER = 0x8055085D; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UNMATCH_SEQUENCE = 0x8055085E; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_SAVEDATA_NOT_FOUND = 0x8055085F; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_MANY_SAVEDATA_FILES = 0x80550860; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_MUCH_TOTAL_SAVEDATA_SIZE = 0x80550861; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NOT_YET_DOWNLOADABLE = 0x80550862; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_BLACKLISTED_TITLE = 0x80550868; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_LARGE_ICONDATA = 0x80550869; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_LARGE_SAVEDATA = 0x8055086A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UNMATCH_SIGNATURE = 0x8055086B; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UNMATCH_MD5SUM = 0x8055086C; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TOO_MUCH_SAVEDATA_SIZE = 0x8055086D; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RECORD_DATE_IS_NEWER_THAN_COMP_DATE = 0x8055086E; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_CONDITIONS_NOT_SATISFIED = 0x80550873; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UNSUPPORTED_PLATFORM = 0x80550878; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_EXPIRED_SIGNATURE = 0x80550889; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_SAVEDATA_UPDATED = 0x8055088A; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_MATCHING_BEFORE_SERVICE = 0x805508A0; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_MATCHING_END_OF_SERVICE = 0x805508A1; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_MATCHING_MAINTENANCE = 0x805508A2; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_BEFORE_SERVICE = 0x805508A3; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_END_OF_SERVICE = 0x805508A4; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_MAINTENANCE = 0x805508A5; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE = 0x805508A6; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TITLE_USER_STORAGE_BEFORE_SERVICE = 0x805508AA; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TITLE_USER_STORAGE_END_OF_SERVICE = 0x805508AB; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_TITLE_USER_STORAGE_MAINTENANCE = 0x805508AC; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_FSR_BEFORE_SERVICE = 0x805508AD; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_FSR_END_OF_SERVICE = 0x805508AE; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_FSR_MAINTENANCE = 0x805508AF; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UBS_BEFORE_SERVICE = 0x805508B0; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UBS_END_OF_SERVICE = 0x805508B1; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UBS_MAINTENANCE = 0x805508B2; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_BASIC_BLACKLISTED_USER_ID = 0x805508B3; +constexpr int ORBIS_NP_COMMUNITY_SERVER_ERROR_UNSPECIFIED = 0x805508FF; + +// NP Matching2 (0x80550c01 – 0x80550d33) +constexpr int ORBIS_NP_MATCHING2_ERROR_OUT_OF_MEMORY = 0x80550C01; +constexpr int ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = 0x80550C02; +constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED = 0x80550C03; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_MAX = 0x80550C04; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_EXISTS = 0x80550C05; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND = 0x80550C06; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_STARTED = 0x80550C07; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED = 0x80550C08; +constexpr int ORBIS_NP_MATCHING2_ERROR_SERVER_NOT_FOUND = 0x80550C09; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT = 0x80550C0A; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID = 0x80550C0B; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_SERVER_ID = 0x80550C0C; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_WORLD_ID = 0x80550C0D; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_LOBBY_ID = 0x80550C0E; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ROOM_ID = 0x80550C0F; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_MEMBER_ID = 0x80550C10; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ATTRIBUTE_ID = 0x80550C11; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_CASTTYPE = 0x80550C12; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_SORT_METHOD = 0x80550C13; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_MAX_SLOT = 0x80550C14; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_MATCHING_SPACE = 0x80550C16; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_BLOCK_KICK_FLAG = 0x80550C17; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_MESSAGE_TARGET = 0x80550C18; +constexpr int ORBIS_NP_MATCHING2_ERROR_RANGE_FILTER_MAX = 0x80550C19; +constexpr int ORBIS_NP_MATCHING2_ERROR_INSUFFICIENT_BUFFER = 0x80550C1A; +constexpr int ORBIS_NP_MATCHING2_ERROR_DESTINATION_DISAPPEARED = 0x80550C1B; +constexpr int ORBIS_NP_MATCHING2_ERROR_REQUEST_TIMEOUT = 0x80550C1C; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ALIGNMENT = 0x80550C1D; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONNECTION_CLOSED_BY_SERVER = 0x80550C1E; +constexpr int ORBIS_NP_MATCHING2_ERROR_SSL_VERIFY_FAILED = 0x80550C1F; +constexpr int ORBIS_NP_MATCHING2_ERROR_SSL_HANDSHAKE = 0x80550C20; +constexpr int ORBIS_NP_MATCHING2_ERROR_SSL_SEND = 0x80550C21; +constexpr int ORBIS_NP_MATCHING2_ERROR_SSL_RECV = 0x80550C22; +constexpr int ORBIS_NP_MATCHING2_ERROR_JOINED_SESSION_MAX = 0x80550C23; +constexpr int ORBIS_NP_MATCHING2_ERROR_ALREADY_JOINED = 0x80550C24; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_SESSION_TYPE = 0x80550C25; +constexpr int ORBIS_NP_MATCHING2_ERROR_NP_SIGNED_OUT = 0x80550C26; +constexpr int ORBIS_NP_MATCHING2_ERROR_BUSY = 0x80550C27; +constexpr int ORBIS_NP_MATCHING2_ERROR_SERVER_NOT_AVAILABLE = 0x80550C28; +constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_ALLOWED = 0x80550C29; +constexpr int ORBIS_NP_MATCHING2_ERROR_ABORTED = 0x80550C2A; +constexpr int ORBIS_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND = 0x80550C2B; +constexpr int ORBIS_NP_MATCHING2_ERROR_SESSION_DESTROYED = 0x80550C2C; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_STOPPED = 0x80550C2D; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_REQUEST_PARAMETER = 0x80550C2E; +constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_NP_SIGN_IN = 0x80550C2F; +constexpr int ORBIS_NP_MATCHING2_ERROR_ROOM_NOT_FOUND = 0x80550C30; +constexpr int ORBIS_NP_MATCHING2_ERROR_ROOM_MEMBER_NOT_FOUND = 0x80550C31; +constexpr int ORBIS_NP_MATCHING2_ERROR_LOBBY_NOT_FOUND = 0x80550C32; +constexpr int ORBIS_NP_MATCHING2_ERROR_LOBBY_MEMBER_NOT_FOUND = 0x80550C33; +constexpr int ORBIS_NP_MATCHING2_ERROR_KEEPALIVE_TIMEOUT = 0x80550C34; +constexpr int ORBIS_NP_MATCHING2_ERROR_TIMEOUT_TOO_SHORT = 0x80550C35; +constexpr int ORBIS_NP_MATCHING2_ERROR_TIMEDOUT = 0x80550C36; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_SLOTGROUP = 0x80550C37; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ATTRIBUTE_SIZE = 0x80550C38; +constexpr int ORBIS_NP_MATCHING2_ERROR_CANNOT_ABORT = 0x80550C39; +constexpr int ORBIS_NP_MATCHING2_ERROR_SESSION_NOT_FOUND = 0x80550C3A; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT = 0x80550C3B; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_BAD_REQUEST = 0x80550D01; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_SERVICE_UNAVAILABLE = 0x80550D02; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_BUSY = 0x80550D03; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_END_OF_SERVICE = 0x80550D04; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_INTERNAL_SERVER_ERROR = 0x80550D05; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_PLAYER_BANNED = 0x80550D06; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_FORBIDDEN = 0x80550D07; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_BLOCKED = 0x80550D08; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_UNSUPPORTED_NP_ENV = 0x80550D09; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_INVALID_TICKET = 0x80550D0A; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_INVALID_SIGNATURE = 0x80550D0B; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_EXPIRED_TICKET = 0x80550D0C; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_ENTITLEMENT_REQUIRED = 0x80550D0D; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_CONTEXT = 0x80550D0E; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_CLOSED = 0x80550D0F; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_TITLE = 0x80550D10; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_WORLD = 0x80550D11; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_LOBBY = 0x80550D12; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_ROOM = 0x80550D13; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_LOBBY_INSTANCE = 0x80550D14; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_ROOM_INSTANCE = 0x80550D15; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_PASSWORD_MISMATCH = 0x80550D17; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_LOBBY_FULL = 0x80550D18; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_ROOM_FULL = 0x80550D19; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_GROUP_FULL = 0x80550D1B; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_USER = 0x80550D1C; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_GROUP_PASSWORD_MISMATCH = 0x80550D1D; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_TITLE_PASSPHRASE_MISMATCH = 0x80550D1E; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_LOBBY_ALREADY_EXIST = 0x80550D25; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_ROOM_ALREADY_EXIST = 0x80550D26; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_CONSOLE_BANNED = 0x80550D28; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_ROOMGROUP = 0x80550D29; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_SUCH_GROUP = 0x80550D2A; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NO_PASSWORD = 0x80550D2B; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_INVALID_GROUP_SLOT_NUM = 0x80550D2C; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_INVALID_PASSWORD_SLOT_MASK = 0x80550D2D; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_DUPLICATE_GROUP_LABEL = 0x80550D2E; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_REQUEST_OVERFLOW = 0x80550D2F; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_ALREADY_JOINED = 0x80550D30; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_NAT_TYPE_MISMATCH = 0x80550D31; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_ROOM_INCONSISTENCY = 0x80550D32; +constexpr int ORBIS_NP_MATCHING2_SERVER_ERROR_BLOCKED_USER_IN_ROOM = 0x80550D33; + +// NP Matching2 Signaling (0x80550e01 – 0x80550e1a) +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_NOT_INITIALIZED = 0x80550E01; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_ALREADY_INITIALIZED = 0x80550E02; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_OUT_OF_MEMORY = 0x80550E03; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CTXID_NOT_AVAILABLE = 0x80550E04; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CTX_NOT_FOUND = 0x80550E05; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_REQID_NOT_AVAILABLE = 0x80550E06; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_REQ_NOT_FOUND = 0x80550E07; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_PARSER_CREATE_FAILED = 0x80550E08; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_PARSER_FAILED = 0x80550E09; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_NAMESPACE = 0x80550E0A; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_NETINFO_NOT_AVAILABLE = 0x80550E0B; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_PEER_NOT_RESPONDING = 0x80550E0C; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CONNID_NOT_AVAILABLE = 0x80550E0D; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CONN_NOT_FOUND = 0x80550E0E; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_PEER_UNREACHABLE = 0x80550E0F; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_PEER = 0x80550E10; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT = 0x80550E11; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CTX_MAX = 0x80550E12; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_RESULT_NOT_FOUND = 0x80550E13; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_CONN_IN_PROGRESS = 0x80550E14; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT = 0x80550E15; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_OWN_NP_ID = 0x80550E16; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_TOO_MANY_CONN = 0x80550E17; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF = 0x80550E18; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_MATCHING2_PEER_NOT_FOUND = 0x80550E19; +constexpr int ORBIS_NP_MATCHING2_SIGNALING_ERROR_OWN_PEER_ADDRESS = 0x80550E1A; + +// NP Trophy (0x80551600 – 0x805516c2) +constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602; +constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605; +constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606; +constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C; +constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D; +constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; +constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E; +constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_TITLE_ID = 0x80551620; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623; +constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627; +constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TITLE = 0x8055162A; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_GOTO_PROCESS_SUSPEND = 0x8055162C; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_ON_MUTE = 0x8055162E; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_TOO_BIG = 0x8055162F; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_RETRY_COUNT_MAX = 0x80551630; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND = 0x805516C2; + +// NP Bandwidth Test (0x80551f02 – 0x80551f09) +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_NOT_INITIALIZED = 0x80551F02; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_BAD_RESPONSE = 0x80551F03; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_OUT_OF_MEMORY = 0x80551F04; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_INVALID_ARGUMENT = 0x80551F05; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_INVALID_SIZE = 0x80551F06; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_CONTEXT_NOT_AVAILABLE = 0x80551F07; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_ABORTED = 0x80551F08; +constexpr int ORBIS_NP_BANDWIDTH_TEST_ERROR_TIMEOUT = 0x80551F09; + +// NP Party (0x80552501 – 0x80552516) +constexpr int ORBIS_NP_PARTY_ERROR_UNKNOWN = 0x80552501; +constexpr int ORBIS_NP_PARTY_ERROR_ALREADY_INITIALIZED = 0x80552502; +constexpr int ORBIS_NP_PARTY_ERROR_NOT_INITIALIZED = 0x80552503; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_ARGUMENT = 0x80552504; +constexpr int ORBIS_NP_PARTY_ERROR_OUT_OF_MEMORY = 0x80552505; +constexpr int ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY = 0x80552506; +constexpr int ORBIS_NP_PARTY_ERROR_VOICE_NOT_ENABLED = 0x80552507; +constexpr int ORBIS_NP_PARTY_ERROR_MEMBER_NOT_FOUND = 0x80552508; +constexpr int ORBIS_NP_PARTY_ERROR_SEND_BUSY = 0x80552509; +constexpr int ORBIS_NP_PARTY_ERROR_SEND_OUT_OF_CONTEXT = 0x80552510; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_STATE = 0x80552511; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_LOCAL_PARTY_MEMBER = 0x80552512; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_PROCESS_TYPE = 0x80552513; +constexpr int ORBIS_NP_PARTY_ERROR_GAME_SESSION_NOT_ENABLED = 0x80552514; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_PARTY_NO_FRIENDS = 0x80552515; +constexpr int ORBIS_NP_PARTY_ERROR_INVALID_PARTY_IS_GAME_SESSION = 0x80552516; + +// NP Signaling (0x80552701 – 0x8055271b) +constexpr int ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED = 0x80552701; +constexpr int ORBIS_NP_SIGNALING_ERROR_ALREADY_INITIALIZED = 0x80552702; +constexpr int ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY = 0x80552703; +constexpr int ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE = 0x80552704; +constexpr int ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND = 0x80552705; +constexpr int ORBIS_NP_SIGNALING_ERROR_REQID_NOT_AVAILABLE = 0x80552706; +constexpr int ORBIS_NP_SIGNALING_ERROR_REQ_NOT_FOUND = 0x80552707; +constexpr int ORBIS_NP_SIGNALING_ERROR_PARSER_CREATE_FAILED = 0x80552708; +constexpr int ORBIS_NP_SIGNALING_ERROR_PARSER_FAILED = 0x80552709; +constexpr int ORBIS_NP_SIGNALING_ERROR_INVALID_NAMESPACE = 0x8055270A; +constexpr int ORBIS_NP_SIGNALING_ERROR_NETINFO_NOT_AVAILABLE = 0x8055270B; +constexpr int ORBIS_NP_SIGNALING_ERROR_PEER_NOT_RESPONDING = 0x8055270C; +constexpr int ORBIS_NP_SIGNALING_ERROR_CONNID_NOT_AVAILABLE = 0x8055270D; +constexpr int ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND = 0x8055270E; +constexpr int ORBIS_NP_SIGNALING_ERROR_PEER_UNREACHABLE = 0x8055270F; +constexpr int ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER = 0x80552710; +constexpr int ORBIS_NP_SIGNALING_ERROR_TIMEOUT = 0x80552711; +constexpr int ORBIS_NP_SIGNALING_ERROR_CTX_MAX = 0x80552712; +constexpr int ORBIS_NP_SIGNALING_ERROR_RESULT_NOT_FOUND = 0x80552713; +constexpr int ORBIS_NP_SIGNALING_ERROR_CONN_IN_PROGRESS = 0x80552714; +constexpr int ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT = 0x80552715; +constexpr int ORBIS_NP_SIGNALING_ERROR_OWN_NP_ID = 0x80552716; +constexpr int ORBIS_NP_SIGNALING_ERROR_TOO_MANY_CONN = 0x80552717; +constexpr int ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_MYSELF = 0x80552718; +constexpr int ORBIS_NP_SIGNALING_ERROR_PROHIBITED_TO_USE = 0x80552719; +constexpr int ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT = 0x8055271A; +constexpr int ORBIS_NP_SIGNALING_ERROR_OWN_PEER_ADDRESS = 0x8055271B; + +// NP WebAPI (0x80552901 – 0x8055291f) +constexpr int ORBIS_NP_WEBAPI_ERROR_OUT_OF_MEMORY = 0x80552901; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT = 0x80552902; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = 0x80552903; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80552904; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = 0x80552905; +constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = 0x80552906; +constexpr int ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN = 0x80552907; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = 0x80552908; +constexpr int ORBIS_NP_WEBAPI_ERROR_ABORTED = 0x80552909; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055290A; +constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290B; +constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290C; +constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = 0x8055290D; +constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290E; +constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290F; +constexpr int ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x80552910; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY = 0x80552911; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY = 0x80552912; +constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY = 0x80552913; +constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_HTTP_STATUS_CODE = 0x80552914; +constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_HTTP_HEADER = 0x80552915; +constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_FUNCTION_CALL = 0x80552916; +constexpr int ORBIS_NP_WEBAPI_ERROR_MULTIPART_PART_NOT_FOUND = 0x80552917; +constexpr int ORBIS_NP_WEBAPI_ERROR_PARAMETER_TOO_LONG = 0x80552918; +constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY = 0x80552919; +constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = 0x8055291A; +constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX = 0x8055291B; +constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055291C; +constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055291D; +constexpr int ORBIS_NP_WEBAPI_ERROR_AFTER_SEND = 0x8055291E; +constexpr int ORBIS_NP_WEBAPI_ERROR_TIMEOUT = 0x8055291F; + +// NP In-Game Message (0x80552b01 – 0x80552b09) +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_OUT_OF_MEMORY = 0x80552B01; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_INVALID_ARGUMENT = 0x80552B02; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80552B03; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_NOT_SIGNED_IN = 0x80552B04; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_HANDLE_NOT_FOUND = 0x80552B05; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_ABORTED = 0x80552B06; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x80552B07; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_NOT_PREPARED = 0x80552B08; +constexpr int ORBIS_NP_IN_GAME_MESSAGE_ERROR_EXCEED_RATE_LIMIT = 0x80552B09; + +// NP ID Mapper (0x80553000 – 0x80553003) +constexpr int ORBIS_NP_ID_MAPPER_ERROR_ABORTED = 0x80553000; +constexpr int ORBIS_NP_ID_MAPPER_ERROR_ACCOUNT_ID_NOT_FOUND = 0x80553001; +constexpr int ORBIS_NP_ID_MAPPER_ERROR_ONLINE_ID_NOT_FOUND = 0x80553002; +constexpr int ORBIS_NP_ID_MAPPER_ERROR_NP_ID_NOT_FOUND = 0x80553003; + +// NP Data Communication (0x80553200 – 0x80553218) +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_UNKNOWN = 0x80553200; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_NOT_INITIALIZED = 0x80553201; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_ALREADY_INITIALIZED = 0x80553202; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_OUT_OF_MEMORY = 0x80553203; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_SIZE = 0x80553204; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_ARGUMENT = 0x80553205; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_CONTEXT_ID = 0x80553206; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_USER_ID = 0x80553207; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_SERVICE_LABEL = 0x80553208; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_EVENT_HANDLER = 0x80553209; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_PEER_ADDRESS = 0x8055320A; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_ACCOUNT_ID = 0x8055320B; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_PLATFORM_TYPE = 0x8055320C; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_SIGNALING_CONNECTION_ID = 0x8055320D; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_PEER_CONNECTION_ID = 0x8055320E; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_DATA_CHANNEL_ID = 0x8055320F; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_DATA_CHANNEL_NUMBER = 0x80553210; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_DATA_CHANNEL_OPTION = 0x80553211; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INVALID_DATA_CHANNEL_OPTION_VALUE = 0x80553212; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_USER_NOT_LOGGED_IN = 0x80553213; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_USER_NOT_SIGNED_IN = 0x80553214; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_CONTEXT_EXCEEDS_MAX = 0x80553215; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_SIGNALING_EXCEEDS_MAX = 0x80553216; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_DATA_CHANNEL_EXCEEDS_MAX = 0x80553217; +constexpr int ORBIS_NP_DATA_COMMUNICATION_ERROR_INSUFFICIENT_BUFFER = 0x80553218; + +// NP Session Signaling (0x80553301 – 0x80553312) +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED = 0x80553301; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_ALREADY_INITIALIZED = 0x80553302; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT = 0x80553303; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_OWN_PEER_ADDRESS = 0x80553304; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_OUT_OF_MEMORY = 0x80553305; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_TIMEOUT = 0x80553306; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_CTXID_NOT_AVAILABLE = 0x80553307; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_CTX_NOT_FOUND = 0x80553308; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_GRPID_NOT_AVAILABLE = 0x80553309; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_GRP_NOT_FOUND = 0x8055330A; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_CONNID_NOT_AVAILABLE = 0x8055330B; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND = 0x8055330C; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_PEER_UNREACHABLE = 0x8055330D; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_CONN_IN_PROGRESS = 0x8055330E; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_TERMINATED_BY_PEER = 0x8055330F; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_TERMINATED_BY_MYSELF = 0x80553310; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_TOO_MANY_CONN = 0x80553311; +constexpr int ORBIS_NP_SESSION_SIGNALING_ERROR_GROUP_SIGNALING_ALREADY_ACTIVATED = 0x80553312; + +// NP WEBAPI2 (0x80553401 – 0x8055341c) +constexpr int ORBIS_NP_WEBAPI2_ERROR_OUT_OF_MEMORY = 0x80553401; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = 0x80553402; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID = 0x80553403; +constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80553404; +constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND = 0x80553405; +constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406; +constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408; +constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d; +constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410; +constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411; +constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414; +constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415; +constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419; +constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a; +constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c; + +// NP Session Management Client (0x80553600 – 0x8055361e) +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_ALREADY_INITIALIZED = 0x80553600; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_NOT_INITIALIZED = 0x80553601; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_OUT_OF_MEMORY = 0x80553602; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_ARGUMENT = 0x80553603; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_USER_ID = 0x80553604; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_ACCOUNT_ID = 0x80553605; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_PLATFORM_TYPE = 0x80553606; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_ONLINE_ID = 0x80553607; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_SESSION_ID = 0x80553608; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_BRIDGE_ID = 0x80553609; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_BRIDGE_TOKEN = 0x8055360A; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_ETAG = 0x8055360B; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_PEER_ADDRESS = 0x8055360C; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_CHANNEL = 0x8055360D; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_MESSAGE = 0x8055360E; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_PUSH_CONTEXT_ID = 0x8055360F; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_SESSION_CUSTOM_DATA = 0x80553610; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_MEMBER_CUSTOM_DATA = 0x80553611; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_FIELDS = 0x80553612; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_ABORTED = 0x80553613; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_UNKNOWN = 0x80553614; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INSUFFICIENT_BUFFER = 0x80553615; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_ITEM_NOT_FOUND = 0x80553616; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_DATA_MALFORMED = 0x80553617; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_TOPOLOGY = 0x80553618; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_SESSION_NAME = 0x80553619; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_DEVICE_ID = 0x8055361A; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_GROUP_ID = 0x8055361B; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_VIEW_NAME = 0x8055361C; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_EVENT = 0x8055361D; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_CLIENT_ERROR_INVALID_MEMBER_ID = 0x8055361E; + +// NP Session Management Manager (0x80553700 – 0x80553736) +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_ALREADY_INITIALIZED = 0x80553700; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_NOT_INITIALIZED = 0x80553701; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_OUT_OF_MEMORY = 0x80553702; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_ARGUMENT = 0x80553703; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_INITIALIZE_PARAMETER = 0x80553704; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_CONTEXT_PARAMETER = 0x80553705; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_HANDLER = 0x80553706; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_CONTEXT_ID = 0x80553707; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_OPTION = 0x80553708; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_ACCOUNT_ID = 0x80553709; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_PLATFORM_TYPE = 0x8055370A; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_ID = 0x8055370B; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_BRIDGE_INFO = 0x8055370C; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_BRIDGE_ID = 0x8055370D; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_BRIDGE_TOKEN = 0x8055370E; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_ETAG = 0x8055370F; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_CHANNEL = 0x80553710; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_MESSAGE = 0x80553711; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_REQUEST = 0x80553712; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_REQUEST_ID = 0x80553713; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_REQUEST_CALLBACK = 0x80553714; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_REQUEST_CALLBACK_ALREADY_REGISTERED = + 0x80553715; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_DATA_VALUE_TYPE = + 0x80553716; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_MEMBER_DATA_VALUE_TYPE = 0x80553717; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_DATA_VALUE = 0x80553718; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_MEMBER_DATA_VALUE = 0x80553719; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_MEMBER_ID = 0x8055371A; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_ABORTED = 0x8055371B; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_CONTEXT_NOT_STARTED = 0x8055371C; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_CONTEXT_ALREADY_STARTED = 0x8055371D; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_UNKNOWN = 0x8055371E; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INSUFFICIENT_BUFFER = 0x8055371F; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_REQUEST_NOT_FOUND = 0x80553720; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_SESSION_NOT_FOUND = 0x80553721; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_SESSION_ALREADY_EXISTS = 0x80553722; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_CONTEXT_SLOT_EXCEEDS_MAX = 0x80553723; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_UNEXPECTED_DEACTIVATED_ERRCODE = 0x80553724; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_CMD_HANDLER_STOPPED = 0x80553725; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_MEMBER_NOT_FOUND = 0x80553726; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_MEMBER_ALREADY_EXISTS = 0x80553727; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_STATE = 0x80553728; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_SESSION_RELEASED = 0x80553729; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_MEMBER_FULL = 0x8055372A; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_CONTEXT_TYPE = 0x8055372B; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_REQUEST_NOT_FINISHED = 0x8055372C; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_QUEUE_EMPTY = 0x8055372D; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_PRESENCE_OFFLINE_DETECTED = 0x8055372E; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_PROPERTY = 0x8055372F; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_PROPERTY_NOT_FOUND = 0x80553730; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_UNSUPPORTED_OPERATION = 0x80553731; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_GROUP_ID = 0x80553732; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_VIEW_NAME = 0x80553733; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_BRIDGE_INFO_NOT_FOUND = 0x80553734; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_ITEM_NOT_FOUND = 0x80553735; +constexpr int ORBIS_NP_SESSION_MANAGEMENT_MANAGER_ERROR_INVALID_SESSION_CUSTOM_DATA = 0x80553736; + +// NP Game Intent (0x80553800 – 0x80553807) +constexpr int ORBIS_NP_GAME_INTENT_ERROR_UNKNOWN = 0x80553800; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_ALREADY_INITIALIZED = 0x80553801; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_NOT_INITIALIZED = 0x80553802; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_OUT_OF_MEMORY = 0x80553803; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_INVALID_ARGUMENT = 0x80553804; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_INSUFFICIENT_BUFFER = 0x80553805; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_INTENT_NOT_FOUND = 0x80553806; +constexpr int ORBIS_NP_GAME_INTENT_ERROR_VALUE_NOT_FOUND = 0x80553807; + +// NP ASM Client (0x8055a287 – 0x8055a289) +constexpr int ORBIS_NP_ASM_CLIENT_ERROR_ABORTED = 0x8055A287; +constexpr int ORBIS_NP_ASM_CLIENT_ERROR_NP_SERVICE_LAVEL_NOT_MATCH = 0x8055A289; diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp new file mode 100644 index 000000000..5b09bcacd --- /dev/null +++ b/src/core/libraries/np/np_handler.cpp @@ -0,0 +1,1495 @@ +// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/emulator_settings.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_score/np_score.h" +#include "core/user_settings.h" +#include "np_handler.h" +#include "shadnet.pb.h" + +namespace Libraries::Np { + +// Static empty NpId returned when user_id is not connected. +const OrbisNpId NpHandler::s_empty_np_id{}; + +NpHandler& NpHandler::GetInstance() { + static NpHandler s_instance; + return s_instance; +} + +void NpHandler::Initialize() { + if (m_initialized.exchange(true)) { + LOG_WARNING(NpHandler, "Initialize called more than once"); + return; + } + + if (!EmulatorSettings.IsShadNetEnabled()) { + LOG_INFO(NpHandler, "shadNet disabled globally we are in offline mode"); + return; + } + + // Parse server address from GeneralSettings + const std::string server_str = EmulatorSettings.GetShadNetServer(); + std::string host = server_str; + u16 port = 31313; // default port + const auto colon = server_str.rfind(':'); + if (colon != std::string::npos) { + host = server_str.substr(0, colon); + try { + port = static_cast(std::stoi(server_str.substr(colon + 1))); + } catch (...) { + } + } + + const auto logged_in = UserManagement.GetLoggedInUsers(); // get all login users + int connected_count = 0; + + for (int i = 0; i < Libraries::UserService::ORBIS_USER_SERVICE_MAX_LOGIN_USERS; ++i) { + const User* u = logged_in[i]; + if (!u) + continue; + // skip users that has shadnet disabled + if (!u->shadnet_enabled) { + LOG_DEBUG(NpHandler, "user_id={} ('{}') shadNet disabled,skipping", u->user_id, + u->user_name); + continue; + } + // skip also users that doesn't have npid or password empty + if (u->shadnet_npid.empty() || u->shadnet_password.empty()) { + LOG_WARNING(NpHandler, + "user_id={} ('{}') shadNet enabled but credentials missing,skipping", + u->user_id, u->user_name); + continue; + } + + if (ConnectUser(u->user_id, host, port, u->shadnet_npid, u->shadnet_password, + u->shadnet_token)) + ++connected_count; + } + + if (connected_count == 0) { + LOG_WARNING(NpHandler, "no users connected to shadNet"); + return; + } + + // Start the health-monitor worker + m_worker_running = true; + m_worker_thread = std::thread(&NpHandler::WorkerThread, this); +} + +void NpHandler::Shutdown() { + if (!m_initialized.exchange(false)) + return; + + m_worker_running = false; + + // Collect user IDs to disconnect (avoid holding m_mutex_clients during Stop) + std::vector ids; + { + std::lock_guard lock(m_mutex_clients); + for (auto& [uid, _] : m_clients) + ids.push_back(uid); + } + for (s32 uid : ids) + DisconnectUser(uid); + + if (m_worker_thread.joinable()) + m_worker_thread.join(); + + LOG_INFO(NpHandler, "Shutdown complete"); +} + +bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid, + const std::string& password, const std::string& token) { + LOG_INFO(NpHandler, "Connecting user_id={} npid='{}' to {}:{} (timeout {}s)", user_id, npid, + host, port, ShadNet::SHAD_CONNECT_TIMEOUT_MS / 1000); + + auto client = std::make_shared(); + + // Wire per-user notification callbacks + client->onFriendQuery = [this, user_id](const ShadNet::NotifyFriendQuery& n) { + OnFriendQuery(user_id, n); + }; + client->onFriendNew = [this, user_id](const ShadNet::NotifyFriendNew& n) { + OnFriendNew(user_id, n); + }; + client->onFriendLost = [this, user_id](const ShadNet::NotifyFriendLost& n) { + OnFriendLost(user_id, n); + }; + client->onFriendStatus = [this, user_id](const ShadNet::NotifyFriendStatus& n) { + OnFriendStatus(user_id, n); + }; + client->onAsyncReply = [this, user_id](ShadNet::CommandType cmd, u64 pkt_id, + ShadNet::ErrorType err, const std::vector& body) { + OnScoreReply(user_id, cmd, pkt_id, err, body); + }; + + client->Start(host, port, npid, password, token); + + const ShadNet::ShadNetState conn_state = client->WaitForConnection(); + if (conn_state != ShadNet::ShadNetState::Ok) { + LOG_ERROR(NpHandler, "user_id={} connection failed (state={})", user_id, + static_cast(conn_state)); + client->Stop(); + return false; + } + + const ShadNet::ShadNetState auth_state = client->WaitForAuthenticated(); + if (auth_state != ShadNet::ShadNetState::Ok) { + LOG_ERROR(NpHandler, "user_id={} authentication failed (state={})", user_id, + static_cast(auth_state)); + client->Stop(); + return false; + } + + LOG_INFO(NpHandler, "user_id={} signed in npid='{}' accountId={}", user_id, npid, + client->GetUserId()); + + // Build OrbisNpId + { + OrbisNpId np_id{}; + strncpy(np_id.handle.data, npid.c_str(), sizeof(np_id.handle.data) - 1); + std::lock_guard lock(m_mutex_clients); + m_np_ids[user_id] = np_id; + m_clients[user_id] = std::move(client); + } + + FireStateCallback(user_id, NpManager::OrbisNpState::SignedIn); + return true; +} + +void NpHandler::DisconnectUser(s32 user_id) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) + return; + client = std::move(it->second); + m_clients.erase(it); + } + client->Stop(); + FireStateCallback(user_id, NpManager::OrbisNpState::SignedOut); + LOG_INFO(NpHandler, "user_id={} disconnected", user_id); +} + +void NpHandler::WorkerThread() { + constexpr auto INTERVAL = std::chrono::milliseconds(500); + + while (m_worker_running) { + std::this_thread::sleep_for(INTERVAL); + + // Collect ids of dropped clients + std::vector dropped; + { + std::lock_guard lock(m_mutex_clients); + for (auto& [uid, client] : m_clients) { + if (!client->IsConnected()) + dropped.push_back(uid); + } + } + + for (s32 uid : dropped) { + LOG_WARNING(NpHandler, "NpHandler: user_id={} connection dropped", uid); + DisconnectUser(uid); + } + } +} + +bool NpHandler::IsPsnSignedIn(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() && it->second->IsAuthenticated(); +} + +bool NpHandler::IsAnySignedIn() const { + std::lock_guard lock(m_mutex_clients); + for (auto& [_, client] : m_clients) + if (client->IsAuthenticated()) + return true; + return false; +} + +const OrbisNpId& NpHandler::GetNpId(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_np_ids.find(user_id); + return it != m_np_ids.end() ? it->second : s_empty_np_id; +} + +const OrbisNpOnlineId& NpHandler::GetOnlineId(s32 user_id) const { + return GetNpId(user_id).handle; +} + +std::string NpHandler::GetAvatarUrl(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() ? it->second->GetAvatarUrl() : std::string{}; +} + +OrbisNpAccountId NpHandler::GetAccountId(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() ? static_cast(it->second->GetUserId()) : 0; +} + +u32 NpHandler::GetLocalIpAddr(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() ? it->second->GetAddrLocal() : 0; +} + +s32 NpHandler::GetUserIdByAccountId(u64 account_id) const { + std::lock_guard lock(m_mutex_clients); + for (auto& [uid, client] : m_clients) { + if (static_cast(client->GetUserId()) == account_id) + return uid; + } + return -1; +} + +s32 NpHandler::GetUserIdByOnlineId(const OrbisNpOnlineId& online_id) const { + std::lock_guard lock(m_mutex_clients); + for (auto& [uid, np_id] : m_np_ids) { + if (strncmp(np_id.handle.data, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH) == 0) + return uid; + } + return -1; +} + +// friends calls +u32 NpHandler::GetNumFriends(s32 user_id) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() ? it->second->GetNumFriends() : 0; +} + +std::optional NpHandler::GetFriendNpid(s32 user_id, u32 index) const { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + return it != m_clients.end() ? it->second->GetFriendNpid(index) : std::nullopt; +} + +void NpHandler::OnFriendQuery(s32 user_id, const ShadNet::NotifyFriendQuery& n) { + LOG_INFO(NpHandler, "NpHandler: user_id={} FriendQuery from '{}'", user_id, n.fromNpid); + // TODO: enqueue for sceNpCheckCallback dispatch +} + +void NpHandler::OnFriendNew(s32 user_id, const ShadNet::NotifyFriendNew& n) { + LOG_INFO(NpHandler, "NpHandler: user_id={} FriendNew '{}' ({})", user_id, n.npid, + n.online ? "online" : "offline"); +} + +void NpHandler::OnFriendLost(s32 user_id, const ShadNet::NotifyFriendLost& n) { + LOG_INFO(NpHandler, "NpHandler: user_id={} FriendLost '{}'", user_id, n.npid); +} + +void NpHandler::OnFriendStatus(s32 user_id, const ShadNet::NotifyFriendStatus& n) { + LOG_DEBUG(NpHandler, "NpHandler: user_id={} FriendStatus '{}' is {}", user_id, n.npid, + n.online ? "online" : "offline"); +} + +// state callbacks +s32 NpHandler::RegisterStateCallback(StateCallback cb, void* userdata) { + std::lock_guard lock(m_mutex_cbs); + const s32 h = m_next_handle++; + m_state_cbs.push_back({h, std::move(cb), userdata}); + return h; +} + +void NpHandler::UnregisterStateCallback(s32 handle) { + std::lock_guard lock(m_mutex_cbs); + m_state_cbs.erase(std::remove_if(m_state_cbs.begin(), m_state_cbs.end(), + [handle](const CbEntry& e) { return e.handle == handle; }), + m_state_cbs.end()); +} + +void NpHandler::FireStateCallback(s32 user_id, NpManager::OrbisNpState state) { + std::lock_guard lock(m_mutex_cbs); + for (const auto& e : m_state_cbs) { + if (e.cb) + e.cb(user_id, state); + } +} + +// Score + +std::string NpHandler::GetNpCommId(s32 service_label) const { + // TODO complete guess of how commid is mapping to service_label. + constexpr size_t COM_ID_LEN = 12; + const auto& ids = Common::ElfInfo::Instance().GetNpCommIds(); + std::string com_id; + if (!ids.empty()) { + const size_t idx = (service_label >= 0 && static_cast(service_label) < ids.size()) + ? static_cast(service_label) + : 0; + com_id = ids[idx]; + if (static_cast(service_label) >= ids.size()) { + LOG_WARNING(NpHandler, + "GetNpCommId: service_label={} >= npbind entry count {} — falling back " + "to index 0", + service_label, ids.size()); + } + } + if (com_id.size() < COM_ID_LEN) { + com_id.resize(COM_ID_LEN, '\0'); + } else if (com_id.size() > COM_ID_LEN) { + com_id.resize(COM_ID_LEN); + } + return com_id; +} + +s32 NpHandler::RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score, + const char* comment, size_t commentLen, const u8* gameInfoData, + size_t gameInfoSize, std::shared_ptr req) { + // Look up the user's shadNet session. + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "RecordScore: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "RecordScore: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + // Build RecordScoreRequest proto. + shadnet::RecordScoreRequest proto; + proto.set_boardid(boardId); + proto.set_pcid(pcId); + proto.set_score(score); + if (comment && commentLen > 0) { + proto.set_comment(std::string(comment, commentLen)); + } + if (gameInfoData && gameInfoSize > 0) { + proto.set_data(std::string(reinterpret_cast(gameInfoData), gameInfoSize)); + } + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::RecordScore, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::RecordScore; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "RecordScore: user_id={} service_label={} board={} pcId={} score={} commentLen={} " + "gameInfoSize={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, pcId, score, commentLen, gameInfoSize, pkt_id, + com_id); + return ORBIS_OK; +} + +s32 NpHandler::RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score, + const u8* data, size_t size, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "RecordGameData: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "RecordGameData: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::RecordScoreGameDataRequest proto; + proto.set_boardid(boardId); + proto.set_pcid(pcId); + proto.set_score(score); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size() + size); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + if (data != nullptr && size > 0) { + payload.insert(payload.end(), data, data + size); + } + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::RecordScoreData, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::RecordScoreData; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "RecordGameData: user_id={} service_label={} board={} pcId={} score={} dataSize={} " + "pkt_id={} com_id='{}'", + user_id, service_label, boardId, pcId, score, size, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetGameData(s32 user_id, s32 service_label, u32 boardId, const std::string& npId, + s32 pcId, void* dataOut, u64 recvSize, u64* totalSizeOut, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetGameData: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetGameData: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreGameDataRequest proto; + proto.set_boardid(boardId); + proto.set_npid(npId); + proto.set_pcid(pcId); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreData, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreData; + pending.dataOut = dataOut; + pending.recvSize = recvSize; + pending.totalSizeOut = totalSizeOut; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetGameData: user_id={} service_label={} board={} npId='{}' pcId={} recvSize={} " + "pkt_id={} com_id='{}'", + user_id, service_label, boardId, npId, pcId, recvSize, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardId, u64 accountId, + s32 pcId, void* dataOut, u64 recvSize, u64* totalSizeOut, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreGameDataByAccountIdRequest proto; + proto.set_boardid(boardId); + proto.set_accountid(accountId); + proto.set_pcid(pcId); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = + client->SubmitRequest(ShadNet::CommandType::GetScoreGameDataByAccId, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreGameDataByAccId; + pending.dataOut = dataOut; + pending.recvSize = recvSize; + pending.totalSizeOut = totalSizeOut; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetGameDataByAccountId: user_id={} service_label={} board={} accountId={} pcId={} " + "recvSize={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, accountId, pcId, recvSize, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetBoardInfo(s32 user_id, s32 service_label, u32 boardId, + NpScore::OrbisNpScoreBoardInfo* boardInfo, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + const std::string com_id = GetNpCommId(service_label); + std::vector payload; + payload.reserve(12 + 4); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + payload.push_back(static_cast(boardId)); + payload.push_back(static_cast(boardId >> 8)); + payload.push_back(static_cast(boardId >> 16)); + payload.push_back(static_cast(boardId >> 24)); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetBoardInfos, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetBoardInfos; + pending.boardInfo = boardInfo; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, "GetBoardInfo: user_id={} service_label={} board={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId, + const std::vector& npIds, + const std::vector& pcIds, + NpScore::OrbisNpScorePlayerRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + // pcIds must either be empty (use 0 for everything) or match npIds in size. + if (!pcIds.empty() && pcIds.size() != npIds.size()) { + LOG_ERROR(NpHandler, "GetRankingByNpId: pcIds size {} != npIds size {}", pcIds.size(), + npIds.size()); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + // Look up the user's session. + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreNpIdRequest proto; + proto.set_boardid(boardId); + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + for (size_t i = 0; i < npIds.size(); ++i) { + auto* entry = proto.add_npids(); + entry->set_npid(npIds[i]); + entry->set_pcid(pcIds.empty() ? 0 : pcIds[i]); + } + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreNpid, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreNpid; + pending.requestedNpIds = npIds; + pending.rankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = npIds.size(); + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetRankingByNpId: user_id={} service_label={} board={} npIdCount={} " + "withPcId={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, npIds.size(), !pcIds.empty(), commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank, + u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreRangeRequest proto; + proto.set_boardid(boardId); + proto.set_startrank(startSerialRank); + proto.set_numranks(arrayNum); + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreRange, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreRange; + pending.plainRankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = arrayNum; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetRankingByRange: user_id={} service_label={} board={} startRank={} numRanks={} " + "withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank, + u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreRangeRequest proto; + proto.set_boardid(boardId); + proto.set_startrank(startSerialRank); + proto.set_numranks(arrayNum); + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreRange, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreRange; + // Note: aRankArray is set, plainRankArray remains null. The shared + // GetScoreRange/GetScoreFriends branch in OnScoreReply dispatches + // on which one is non-null to pick the right fill helper. + pending.aRankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = arrayNum; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetRankingByRangeA: user_id={} service_label={} board={} startRank={} numRanks={} " + "withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId, + const std::vector& accountIds, + const std::vector& pcIds, + NpScore::OrbisNpScorePlayerRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetRankingByAccountId: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreAccountIdRequest proto; + proto.set_boardid(boardId); + for (size_t i = 0; i < accountIds.size(); ++i) { + auto* entry = proto.add_ids(); + entry->set_accountid(static_cast(accountIds[i])); + // If the caller provided per-entry pcIds, use them; otherwise all zero. + entry->set_pcid(i < pcIds.size() ? pcIds[i] : 0); + } + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreAccountId, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreAccountId; + pending.aPlayerRankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = accountIds.size(); + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetRankingByAccountId: user_id={} service_label={} board={} accountIdCount={} " + "withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, accountIds.size(), commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bool includeSelf, + u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreFriendsRequest proto; + proto.set_boardid(boardId); + proto.set_includeself(includeSelf); + proto.set_max(arrayNum); + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreFriends, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreFriends; + pending.plainRankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = arrayNum; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetFriendsRanking: user_id={} service_label={} board={} includeSelf={} " + "arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +s32 NpHandler::GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, bool includeSelf, + u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req) { + std::shared_ptr client; + { + std::lock_guard lock(m_mutex_clients); + auto it = m_clients.find(user_id); + if (it == m_clients.end()) { + LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not connected", user_id); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + client = it->second; + } + if (!client->IsAuthenticated()) { + LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not authenticated", user_id); + return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN; + } + + shadnet::GetScoreFriendsRequest proto; + proto.set_boardid(boardId); + proto.set_includeself(includeSelf); + proto.set_max(arrayNum); + proto.set_withcomment(commentArray != nullptr); + proto.set_withgameinfo(infoArray != nullptr); + + const std::string proto_bytes = proto.SerializeAsString(); + const std::string com_id = GetNpCommId(service_label); + + std::vector payload; + payload.reserve(12 + 4 + proto_bytes.size()); + payload.insert(payload.end(), com_id.begin(), com_id.end()); + const u32 sz = static_cast(proto_bytes.size()); + payload.push_back(static_cast(sz)); + payload.push_back(static_cast(sz >> 8)); + payload.push_back(static_cast(sz >> 16)); + payload.push_back(static_cast(sz >> 24)); + payload.insert(payload.end(), proto_bytes.begin(), proto_bytes.end()); + + const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::GetScoreFriends, payload); + { + std::lock_guard lock(m_mutex_pending_score); + PendingScoreRequest pending; + pending.req = std::move(req); + pending.cmd = ShadNet::CommandType::GetScoreFriends; + // Note: aRankArray is set, plainRankArray remains null. OnScoreReply + // dispatches on which one is non-null. + pending.aRankArray = rankArray; + pending.commentArray = commentArray; + pending.infoArray = infoArray; + pending.lastSortDate = lastSortDate; + pending.totalRecord = totalRecord; + pending.arrayNum = arrayNum; + m_pending_score.emplace(pkt_id, std::move(pending)); + } + LOG_INFO(NpHandler, + "GetFriendsRankingA: user_id={} service_label={} board={} includeSelf={} " + "arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'", + user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr, + infoArray != nullptr, pkt_id, com_id); + return ORBIS_OK; +} + +static u32 FillPlainRankArrayFromProto(const shadnet::GetScoreResponse& resp, u64 maxSlots, + NpScore::OrbisNpScoreRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray) { + const int n_resp = resp.rankarray_size(); + u32 out_i = 0; + for (int i = 0; i < n_resp && out_i < maxSlots; ++i) { + const auto& r = resp.rankarray(i); + if (r.npid().empty()) { + continue; + } + auto& out = rankArray[out_i]; + // Copy OnlineId (the npId string) into the 16-byte data field. + const std::string& npid = r.npid(); + const size_t cp = std::min(npid.size(), sizeof(out.npId.handle.data)); + std::memcpy(out.npId.handle.data, npid.data(), cp); + out.npId.handle.term = 0; + LOG_INFO(NpHandler, + "FillPlainRankArrayFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} " + "score={}", + out_i, i, npid, npid.size(), r.rank(), r.score()); + out.pcId = r.pcid(); + out.serialRank = r.rank(); + out.rank = r.rank(); + out.highestRank = r.rank(); + out.hasGameData = r.hasgamedata() ? 1 : 0; + out.scoreValue = r.score(); + out.recordDate.tick = r.recorddate(); + + if (commentArray != nullptr && i < resp.commentarray_size()) { + const std::string& cmt = resp.commentarray(i); + const size_t ccp = + std::min(cmt.size(), sizeof(commentArray[out_i].utf8Comment) - 1); + std::memcpy(commentArray[out_i].utf8Comment, cmt.data(), ccp); + } + if (infoArray != nullptr && i < resp.infoarray_size()) { + const std::string& gi = resp.infoarray(i).data(); + const size_t gcp = std::min(gi.size(), sizeof(infoArray[out_i].data)); + infoArray[out_i].infoSize = static_cast(gcp); + std::memcpy(infoArray[out_i].data, gi.data(), gcp); + } + ++out_i; + } + return out_i; +} + +static u32 FillPlainRankArrayAFromProto(const shadnet::GetScoreResponse& resp, u64 maxSlots, + NpScore::OrbisNpScoreRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray) { + const int n_resp = resp.rankarray_size(); + u32 out_i = 0; + for (int i = 0; i < n_resp && out_i < maxSlots; ++i) { + const auto& r = resp.rankarray(i); + if (r.npid().empty()) { + continue; + } + auto& out = rankArray[out_i]; + // RankDataA stores OnlineId directly (not wrapped in NpId). + const std::string& npid = r.npid(); + const size_t cp = std::min(npid.size(), sizeof(out.onlineId.data)); + std::memcpy(out.onlineId.data, npid.data(), cp); + out.onlineId.term = 0; + LOG_INFO(NpHandler, + "FillPlainRankArrayAFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} " + "score={} accountId={}", + out_i, i, npid, npid.size(), r.rank(), r.score(), r.accountid()); + out.pcId = r.pcid(); + out.serialRank = r.rank(); + out.rank = r.rank(); + out.highestRank = r.rank(); + out.hasGameData = r.hasgamedata() ? 1 : 0; + out.scoreValue = r.score(); + out.recordDate.tick = r.recorddate(); + out.accountId = static_cast(r.accountid()); + + if (commentArray != nullptr && i < resp.commentarray_size()) { + const std::string& cmt = resp.commentarray(i); + const size_t ccp = + std::min(cmt.size(), sizeof(commentArray[out_i].utf8Comment) - 1); + std::memcpy(commentArray[out_i].utf8Comment, cmt.data(), ccp); + } + if (infoArray != nullptr && i < resp.infoarray_size()) { + const std::string& gi = resp.infoarray(i).data(); + const size_t gcp = std::min(gi.size(), sizeof(infoArray[out_i].data)); + infoArray[out_i].infoSize = static_cast(gcp); + std::memcpy(infoArray[out_i].data, gi.data(), gcp); + } + ++out_i; + } + return out_i; +} + +static u32 FillPlayerRankArrayAFromProto(const shadnet::GetScoreResponse& resp, u64 requestedCount, + NpScore::OrbisNpScorePlayerRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray) { + const int n_resp = resp.rankarray_size(); + const int n_req = static_cast(requestedCount); + if (n_resp != n_req) { + LOG_WARNING(NpHandler, + "FillPlayerRankArrayAFromProto: response count {} != request count {} — " + "will fill up to min() and leave extras at hasData=0", + n_resp, n_req); + } + const int n = std::min(n_resp, n_req); + u32 found = 0; + for (int i = 0; i < n; ++i) { + const auto& r = resp.rankarray(i); + if (r.npid().empty()) { + continue; // no score on this board for this input accountId + } + auto& out = rankArray[i]; + out.hasData = 1; + const std::string& npid = r.npid(); + const size_t cp = std::min(npid.size(), sizeof(out.rankData.onlineId.data)); + std::memcpy(out.rankData.onlineId.data, npid.data(), cp); + out.rankData.onlineId.term = 0; + out.rankData.pcId = r.pcid(); + out.rankData.serialRank = r.rank(); + out.rankData.rank = r.rank(); + out.rankData.highestRank = r.rank(); + out.rankData.hasGameData = r.hasgamedata() ? 1 : 0; + out.rankData.scoreValue = r.score(); + out.rankData.recordDate.tick = r.recorddate(); + out.rankData.accountId = static_cast(r.accountid()); + + if (commentArray != nullptr && i < resp.commentarray_size()) { + const std::string& cmt = resp.commentarray(i); + const size_t ccp = + std::min(cmt.size(), sizeof(commentArray[i].utf8Comment) - 1); + std::memcpy(commentArray[i].utf8Comment, cmt.data(), ccp); + } + if (infoArray != nullptr && i < resp.infoarray_size()) { + const std::string& gi = resp.infoarray(i).data(); + const size_t gcp = std::min(gi.size(), sizeof(infoArray[i].data)); + infoArray[i].infoSize = static_cast(gcp); + std::memcpy(infoArray[i].data, gi.data(), gcp); + } + ++found; + } + return found; +} + +static u32 FillRankArrayFromProto(const shadnet::GetScoreResponse& resp, + const std::vector& requestedNpIds, + NpScore::OrbisNpScorePlayerRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray) { + const int n_resp = resp.rankarray_size(); + const int n_req = static_cast(requestedNpIds.size()); + if (n_resp != n_req) { + LOG_WARNING(NpHandler, + "FillRankArrayFromProto: response count {} != request count {} — " + "will fill up to min() and leave extras at hasData=0", + n_resp, n_req); + } + const int n = std::min(n_resp, n_req); + u32 found = 0; + for (int i = 0; i < n; ++i) { + const auto& r = resp.rankarray(i); + if (r.npid().empty()) { + continue; // server signalled "no data for this slot" + } + + auto& out = rankArray[i]; + out.hasData = 1; + const std::string& npid = r.npid(); + const size_t cp = std::min(npid.size(), sizeof(out.rankData.npId.handle.data)); + std::memcpy(out.rankData.npId.handle.data, npid.data(), cp); + out.rankData.npId.handle.term = 0; + out.rankData.pcId = r.pcid(); + out.rankData.serialRank = r.rank(); + out.rankData.rank = r.rank(); + out.rankData.highestRank = r.rank(); + out.rankData.hasGameData = r.hasgamedata() ? 1 : 0; + out.rankData.scoreValue = r.score(); + out.rankData.recordDate.tick = r.recorddate(); + + if (commentArray != nullptr && i < resp.commentarray_size()) { + const std::string& cmt = resp.commentarray(i); + const size_t ccp = + std::min(cmt.size(), sizeof(commentArray[i].utf8Comment) - 1); + std::memcpy(commentArray[i].utf8Comment, cmt.data(), ccp); + } + if (infoArray != nullptr && i < resp.infoarray_size()) { + const std::string& gi = resp.infoarray(i).data(); + const size_t gcp = std::min(gi.size(), sizeof(infoArray[i].data)); + infoArray[i].infoSize = static_cast(gcp); + std::memcpy(infoArray[i].data, gi.data(), gcp); + } + ++found; + } + return found; +} + +void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, + ShadNet::ErrorType error, const std::vector& body) { + PendingScoreRequest pending; + { + std::lock_guard lock(m_mutex_pending_score); + auto it = m_pending_score.find(pkt_id); + if (it == m_pending_score.end()) { + LOG_WARNING(NpHandler, "OnScoreReply: no pending request for pkt_id={} cmd={}", pkt_id, + static_cast(cmd)); + return; + } + pending = std::move(it->second); + m_pending_score.erase(it); + } + auto& req = pending.req; + + // Server-side errors take precedence over success-path parsing. + if (error != ShadNet::ErrorType::NoError) { + s32 orbis_err = ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE; + switch (error) { + case ShadNet::ErrorType::ScoreNotBest: + orbis_err = ORBIS_OK; + break; + case ShadNet::ErrorType::ScoreInvalid: + orbis_err = ORBIS_NP_COMMUNITY_SERVER_ERROR_INVALID_SCORE; + break; + case ShadNet::ErrorType::NotFound: + orbis_err = ORBIS_NP_COMMUNITY_SERVER_ERROR_RANKING_BOARD_MASTER_NOT_FOUND; + break; + case ShadNet::ErrorType::Unauthorized: + orbis_err = ORBIS_NP_COMMUNITY_SERVER_ERROR_FORBIDDEN; + break; + case ShadNet::ErrorType::DbFail: + orbis_err = ORBIS_NP_COMMUNITY_SERVER_ERROR_INTERNAL_SERVER_ERROR; + break; + case ShadNet::ErrorType::Malformed: + case ShadNet::ErrorType::Invalid: + case ShadNet::ErrorType::InvalidInput: + orbis_err = ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE; + break; + default: + break; + } + LOG_WARNING(NpHandler, + "OnScoreReply: user_id={} pkt_id={} cmd={} server_error={} → orbis_err={:#x}", + user_id, pkt_id, static_cast(cmd), static_cast(error), orbis_err); + req->SetResult(orbis_err); + return; + } + + // Per-command success-path parsing. + switch (cmd) { + case ShadNet::CommandType::RecordScore: { + // Reply body = u32 LE rank. + if (req->tmpRankOut != nullptr && body.size() >= 4) { + const u32 rank = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | (static_cast(body[3]) << 24); + *req->tmpRankOut = rank; + LOG_INFO(NpHandler, "OnScoreReply: RecordScore user_id={} pkt_id={} rank={}", user_id, + pkt_id, rank); + } + req->SetResult(ORBIS_OK); + break; + } + + case ShadNet::CommandType::RecordScoreData: { + // Reply body is empty on success — error byte already consumed by + // the caller. Server-side errors are mapped to ErrorType values + // (NotFound / ScoreInvalid / ScoreHasData) which arrive as the + // packet's error byte; if we got here, error == NoError. + LOG_INFO(NpHandler, "OnScoreReply: RecordScoreData user_id={} pkt_id={} ok", user_id, + pkt_id); + req->SetResult(ORBIS_OK); + break; + } + + case ShadNet::CommandType::GetScoreData: + case ShadNet::CommandType::GetScoreGameDataByAccId: { + const char* cmd_name = (cmd == ShadNet::CommandType::GetScoreData) + ? "GetScoreData" + : "GetScoreGameDataByAccId"; + if (body.size() < 4) { + LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 stored_size = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | + (static_cast(body[3]) << 24); + if (static_cast(4) + stored_size > body.size()) { + LOG_ERROR(NpHandler, "OnScoreReply: {} blob size {} exceeds body size {}", cmd_name, + stored_size, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + + // Always populate *totalSizeOut with the actual stored size on + // the server, even when truncating into a smaller dataOut buffer. + // Lets the caller know if they undersized their buffer and need + // to retry with a larger one. + if (pending.totalSizeOut != nullptr) { + *pending.totalSizeOut = stored_size; + } + if (pending.dataOut != nullptr && pending.recvSize > 0) { + const u64 copy_n = std::min(static_cast(stored_size), pending.recvSize); + std::memcpy(pending.dataOut, body.data() + 4, static_cast(copy_n)); + if (copy_n < stored_size) { + LOG_WARNING(NpHandler, + "OnScoreReply: {} truncated user_id={} pkt_id={} stored={} recv={}", + cmd_name, user_id, pkt_id, stored_size, pending.recvSize); + } + } + LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} storedSize={} recvSize={}", + cmd_name, user_id, pkt_id, stored_size, pending.recvSize); + req->SetResult(ORBIS_OK); + break; + } + + case ShadNet::CommandType::GetBoardInfos: { + if (body.size() < 4) { + LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos body too small ({})", body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 proto_size = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | + (static_cast(body[3]) << 24); + if (static_cast(4) + proto_size > body.size()) { + LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto size {} exceeds body size {}", + proto_size, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + shadnet::BoardInfo bi; + if (!bi.ParseFromArray(body.data() + 4, static_cast(proto_size))) { + LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto parse failed"); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + if (pending.boardInfo != nullptr) { + pending.boardInfo->rankLimit = bi.ranklimit(); + pending.boardInfo->updateMode = bi.updatemode(); + pending.boardInfo->sortMode = bi.sortmode(); + pending.boardInfo->uploadNumLimit = bi.uploadnumlimit(); + pending.boardInfo->uploadSizeLimit = bi.uploadsizelimit(); + } + LOG_INFO(NpHandler, + "OnScoreReply: GetBoardInfos user_id={} pkt_id={} rankLimit={} updateMode={} " + "sortMode={} uploadNumLimit={} uploadSizeLimit={}", + user_id, pkt_id, bi.ranklimit(), bi.updatemode(), bi.sortmode(), + bi.uploadnumlimit(), bi.uploadsizelimit()); + req->SetResult(ORBIS_OK); + break; + } + + case ShadNet::CommandType::GetScoreNpid: { + // Reply body = u32 LE proto size + GetScoreResponse proto bytes. + if (body.size() < 4) { + LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid body too small ({})", body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 proto_size = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | + (static_cast(body[3]) << 24); + if (static_cast(4) + proto_size > body.size()) { + LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto size {} exceeds body size {}", + proto_size, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + shadnet::GetScoreResponse resp; + if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { + LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto parse failed"); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 found = FillRankArrayFromProto(resp, pending.requestedNpIds, pending.rankArray, + pending.commentArray, pending.infoArray); + if (pending.lastSortDate != nullptr) { + pending.lastSortDate->tick = resp.lastsortdate(); + } + if (pending.totalRecord != nullptr) { + *pending.totalRecord = resp.totalrecord(); + } + LOG_INFO(NpHandler, + "OnScoreReply: GetScoreNpid user_id={} pkt_id={} found={}/{} totalRecord={}", + user_id, pkt_id, found, pending.arrayNum, resp.totalrecord()); + req->SetResult(static_cast(found)); + break; + } + case ShadNet::CommandType::GetScoreAccountId: { + // Reply body = u32 LE proto size + GetScoreResponse proto bytes. + // Index-aligned with the request's accountIds[] (empty-npid sentinel + // marks "no score on this board"), same contract as GetScoreNpid. + if (body.size() < 4) { + LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId body too small ({})", + body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 proto_size = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | + (static_cast(body[3]) << 24); + if (static_cast(4) + proto_size > body.size()) { + LOG_ERROR(NpHandler, + "OnScoreReply: GetScoreAccountId proto size {} exceeds body size {}", + proto_size, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + shadnet::GetScoreResponse resp; + if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { + LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId proto parse failed"); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 found = + FillPlayerRankArrayAFromProto(resp, pending.arrayNum, pending.aPlayerRankArray, + pending.commentArray, pending.infoArray); + if (pending.lastSortDate != nullptr) { + pending.lastSortDate->tick = resp.lastsortdate(); + } + if (pending.totalRecord != nullptr) { + *pending.totalRecord = resp.totalrecord(); + } + LOG_INFO(NpHandler, + "OnScoreReply: GetScoreAccountId user_id={} pkt_id={} found={}/{} totalRecord={}", + user_id, pkt_id, found, pending.arrayNum, resp.totalrecord()); + req->SetResult(static_cast(found)); + break; + } + + case ShadNet::CommandType::GetScoreRange: + case ShadNet::CommandType::GetScoreFriends: { + const char* cmd_name = + (cmd == ShadNet::CommandType::GetScoreRange) ? "GetScoreRange" : "GetScoreFriends"; + if (body.size() < 4) { + LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + const u32 proto_size = static_cast(body[0]) | (static_cast(body[1]) << 8) | + (static_cast(body[2]) << 16) | + (static_cast(body[3]) << 24); + if (static_cast(4) + proto_size > body.size()) { + LOG_ERROR(NpHandler, "OnScoreReply: {} proto size {} exceeds body size {}", cmd_name, + proto_size, body.size()); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + shadnet::GetScoreResponse resp; + if (!resp.ParseFromArray(body.data() + 4, static_cast(proto_size))) { + LOG_ERROR(NpHandler, "OnScoreReply: {} proto parse failed", cmd_name); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } + u32 found = 0; + if (pending.aRankArray != nullptr) { + found = FillPlainRankArrayAFromProto(resp, pending.arrayNum, pending.aRankArray, + pending.commentArray, pending.infoArray); + } else { + found = FillPlainRankArrayFromProto(resp, pending.arrayNum, pending.plainRankArray, + pending.commentArray, pending.infoArray); + } + if (pending.lastSortDate != nullptr) { + pending.lastSortDate->tick = resp.lastsortdate(); + } + if (pending.totalRecord != nullptr) { + *pending.totalRecord = resp.totalrecord(); + } + LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} found={}/{} totalRecord={}{}", + cmd_name, user_id, pkt_id, found, pending.arrayNum, resp.totalrecord(), + pending.aRankArray != nullptr ? " (A-variant)" : ""); + req->SetResult(static_cast(found)); + break; + } + + default: + LOG_WARNING(NpHandler, "OnScoreReply: unexpected cmd={} pkt_id={}", static_cast(cmd), + pkt_id); + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE); + break; + } +} + +} // namespace Libraries::Np diff --git a/src/core/libraries/np/np_handler.h b/src/core/libraries/np/np_handler.h new file mode 100644 index 000000000..f0a8ede75 --- /dev/null +++ b/src/core/libraries/np/np_handler.h @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/types.h" +#include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_score/np_score.h" +#include "core/libraries/np/np_score/np_score_ctx.h" +#include "core/libraries/np/np_types.h" +#include "core/libraries/rtc/rtc.h" +#include "core/libraries/system/userservice.h" +#include "shadnet/client.h" + +namespace Libraries::Np { + +class NpHandler { +public: + static NpHandler& GetInstance(); + + NpHandler(const NpHandler&) = delete; + NpHandler& operator=(const NpHandler&) = delete; + + // Connect every currently-logged-in user that has shadNet credentials. + void Initialize(); + + // Disconnect all clients, stop worker threads, fire SignedOut for each. + void Shutdown(); + + // True if this specific user is authenticated to the shadNet server. + bool IsPsnSignedIn(s32 user_id) const; + + /// True if any user is currently signed in + bool IsAnySignedIn() const; + + /// Full NP ID for this user, built once from shadnet_npid after login. + const OrbisNpId& GetNpId(s32 user_id) const; + + /// The Online ID embedded in the NP ID (npid.handle). + const OrbisNpOnlineId& GetOnlineId(s32 user_id) const; + + // Avatar URL returned by the server. + std::string GetAvatarUrl(s32 user_id) const; + + // 64-bit account ID assigned by the server. + OrbisNpAccountId GetAccountId(s32 user_id) const; + + // Local IP address (network byte order) as seen at connect time. + u32 GetLocalIpAddr(s32 user_id) const; + + // Reverse lookup: server account_id to local user_id. + // Returns -1 if no connected user owns that account_id. + s32 GetUserIdByAccountId(u64 account_id) const; + + // Reverse lookup: OrbisNpOnlineId to local user_id. + // Scans m_np_ids for a matching handle.data string. + // Returns -1 if no connected user has that Online ID. + s32 GetUserIdByOnlineId(const OrbisNpOnlineId& online_id) const; + + // Friend list + u32 GetNumFriends(s32 user_id) const; + std::optional GetFriendNpid(s32 user_id, u32 index) const; + + // Submit a RecordScore request to the shadNet server. + s32 RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score, + const char* comment, size_t commentLen, const u8* gameInfoData, + size_t gameInfoSize, std::shared_ptr req); + + s32 RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score, + const u8* data, size_t size, std::shared_ptr req); + + s32 GetGameData(s32 user_id, s32 service_label, u32 boardId, const std::string& npId, s32 pcId, + void* dataOut, u64 recvSize, u64* totalSizeOut, + std::shared_ptr req); + + s32 GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardId, u64 accountId, s32 pcId, + void* dataOut, u64 recvSize, u64* totalSizeOut, + std::shared_ptr req); + + s32 GetBoardInfo(s32 user_id, s32 service_label, u32 boardId, + NpScore::OrbisNpScoreBoardInfo* boardInfo, + std::shared_ptr req); + + // Submit a GetRankingByNpId request to the shadNet server. + s32 GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId, + const std::vector& npIds, const std::vector& pcIds, + NpScore::OrbisNpScorePlayerRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + // Submit a GetRankingByRange request to the shadNet server. + s32 GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank, + u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + // A-variant of GetRankingByRange. + s32 GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u32 startSerialRank, + u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + s32 GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId, + const std::vector& accountIds, const std::vector& pcIds, + NpScore::OrbisNpScorePlayerRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + // Submit a GetFriendsRanking request to the shadNet server. + s32 GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bool includeSelf, + u32 arrayNum, NpScore::OrbisNpScoreRankData* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + // A-variant of GetFriendsRanking + s32 GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, bool includeSelf, + u32 arrayNum, NpScore::OrbisNpScoreRankDataA* rankArray, + NpScore::OrbisNpScoreComment* commentArray, + NpScore::OrbisNpScoreGameInfo* infoArray, + Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord, + std::shared_ptr req); + + // State callbacks + using StateCallback = std::function; + + s32 RegisterStateCallback(StateCallback cb, void* userdata); + void UnregisterStateCallback(s32 handle); + +private: + NpHandler() = default; + ~NpHandler() = default; + + /// Connect one user. Blocks until connected+authenticated or failed. + bool ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid, + const std::string& password, const std::string& token); + + // Disconnect and remove one user's client. + void DisconnectUser(s32 user_id); + + void WorkerThread(); + void FireStateCallback(s32 user_id, NpManager::OrbisNpState state); + + // Notification forwarders wired into each client + void OnFriendQuery(s32 user_id, const ShadNet::NotifyFriendQuery& n); + void OnFriendNew(s32 user_id, const ShadNet::NotifyFriendNew& n); + void OnFriendLost(s32 user_id, const ShadNet::NotifyFriendLost& n); + void OnFriendStatus(s32 user_id, const ShadNet::NotifyFriendStatus& n); + + // Async reply dispatch for score commands. Called from the per-user + // ShadNetClient on the reader thread. + void OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, + const std::vector& body); + + // 12-byte NP Communication ID + std::string GetNpCommId(s32 service_label) const; + + // Per-user client map + mutable std::mutex m_mutex_clients; + std::map> m_clients; + // Per-user NP ID built once from shadnet_npid after login. + std::map m_np_ids; + // Returned by GetNpId/GetOnlineId when user_id is not connected. + static const OrbisNpId s_empty_np_id; + + // Score requests awaiting a reply, keyed by the submit packet id. + struct PendingScoreRequest { + std::shared_ptr req; + ShadNet::CommandType cmd; + std::vector requestedNpIds; + NpScore::OrbisNpScorePlayerRankData* rankArray = nullptr; + NpScore::OrbisNpScoreRankData* plainRankArray = nullptr; + NpScore::OrbisNpScoreRankDataA* aRankArray = nullptr; + NpScore::OrbisNpScorePlayerRankDataA* aPlayerRankArray = nullptr; + NpScore::OrbisNpScoreComment* commentArray = nullptr; + NpScore::OrbisNpScoreGameInfo* infoArray = nullptr; + NpScore::OrbisNpScoreBoardInfo* boardInfo = nullptr; + // GetGameData / GetGameDataByAccountId output buffers. + void* dataOut = nullptr; + u64 recvSize = 0; + u64* totalSizeOut = nullptr; + Libraries::Rtc::OrbisRtcTick* lastSortDate = nullptr; + u32* totalRecord = nullptr; + u64 arrayNum = 0; + }; + mutable std::mutex m_mutex_pending_score; + std::map m_pending_score; + + // Worker thread + std::atomic m_initialized{false}; + std::atomic m_worker_running{false}; + std::thread m_worker_thread; + + // State callbacks + struct CbEntry { + s32 handle; + StateCallback cb; + void* userdata; + }; + mutable std::mutex m_mutex_cbs; + std::vector m_state_cbs; + s32 m_next_handle{1}; +}; + +} // namespace Libraries::Np \ No newline at end of file diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index b6851cbdb..2d63df1ea 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -1,28 +1,112 @@ -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include #include +#include "common/elf_info.h" #include "common/logging/log.h" #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/process.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_manager.h" #include "core/tls.h" +#include "core/user_manager.h" +#include "np_handler.h" namespace Libraries::Np::NpManager { static bool g_shadnet_enabled = false; +static s32 g_firmware_version = -1; +using UserId = Libraries::UserService::OrbisUserServiceUserId; + +static void FillCountryCodeFromProfile(UserId user_id, OrbisNpCountryCode* out) { + std::memset(out, 0, sizeof(OrbisNpCountryCode)); + const User* u = UserManagement.GetUserByID(user_id); + const std::string cfg = u ? u->np_country : std::string(); + const bool ok = cfg.size() == 2 && std::isalpha(static_cast(cfg[0])) && + std::isalpha(static_cast(cfg[1])); + const char* src = ok ? cfg.c_str() : "us"; + std::memcpy(out->country_code, src, 2); +} + +static void FillLanguageCodeFromProfile(UserId user_id, OrbisNpLanguageCode* out) { + std::memset(out, 0, sizeof(OrbisNpLanguageCode)); + const User* u = UserManagement.GetUserByID(user_id); + const std::string cfg = u ? u->np_language : std::string(); + const bool ok = cfg.size() == 2 && std::isalpha(static_cast(cfg[0])) && + std::isalpha(static_cast(cfg[1])); + const char* src = ok ? cfg.c_str() : "en"; + std::memcpy(out->code, src, 2); +} + +static s8 GetAgeFromProfile(UserId user_id) { + const User* u = UserManagement.GetUserByID(user_id); + const int v = u ? static_cast(u->np_age) : 0; + if (v <= 0 || v > 127) { + return 13; + } + return static_cast(v); +} + +static void FillDateOfBirthFromProfile(UserId user_id, OrbisNpDate* out) { + const User* u = UserManagement.GetUserByID(user_id); + const std::string s = u ? u->np_date_of_birth : std::string(); + + int y = 0, m = 0, d = 0; + bool ok = s.size() == 10 && s[4] == '-' && s[7] == '-'; + if (ok) { + auto is_digit_at = [&](size_t i) { return std::isdigit(static_cast(s[i])); }; + for (size_t i : {0u, 1u, 2u, 3u, 5u, 6u, 8u, 9u}) { + if (!is_digit_at(i)) { + ok = false; + break; + } + } + } + if (ok) { + y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); + m = (s[5] - '0') * 10 + (s[6] - '0'); + d = (s[8] - '0') * 10 + (s[9] - '0'); + ok = y >= 1900 && y <= 2100 && m >= 1 && m <= 12 && d >= 1 && d <= 31; + } + + out->year = ok ? static_cast(y) : 2000; + out->month = ok ? static_cast(m) : 1; + out->day = ok ? static_cast(d) : 1; +} + static s32 g_active_requests = 0; static std::mutex g_request_mutex; static std::map> g_np_callbacks; static std::mutex g_np_callbacks_mutex; +// Callbacks + +struct NpStateCallbackForNpToolkit { + OrbisNpStateCallbackForNpToolkit func; + void* userdata; +}; +static NpStateCallbackForNpToolkit NpStateCbForNp; + +struct NpStateCallback { + std::variant func; + void* userdata; +}; +static NpStateCallback NpStateCb; + +struct NpReachabilityStateCallback { + OrbisNpReachabilityStateCallback func; + void* userdata; +}; +static NpReachabilityStateCallback NpReachabilityCb; + // Internal types for storing request-related information enum class NpRequestState { None = 0, @@ -39,7 +123,7 @@ struct NpRequest { static std::vector g_requests; -s32 CreateNpRequest(bool async) { +static s32 CreateNpRequest(bool async) { if (g_active_requests == ORBIS_NP_MANAGER_REQUEST_LIMIT) { return ORBIS_NP_ERROR_REQUEST_MAX; } @@ -69,6 +153,41 @@ s32 CreateNpRequest(bool async) { return req_index + ORBIS_NP_MANAGER_REQUEST_ID_OFFSET + 1; } +// Validate a request ID and return the NpRequest*, or nullptr + error code. +// Writes the error code to *out_err when returning nullptr. +static NpRequest* GetRequest(s32 req_id, s32* out_err) { + s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || + g_requests[req_index].state == NpRequestState::None) { + *out_err = ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + return nullptr; + } + auto& req = g_requests[req_index]; + if (req.state == NpRequestState::Complete) { + req.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; + *out_err = ORBIS_NP_ERROR_INVALID_ARGUMENT; + return nullptr; + } + if (req.state == NpRequestState::Aborted) { + req.result = ORBIS_NP_ERROR_ABORTED; + *out_err = ORBIS_NP_ERROR_ABORTED; + return nullptr; + } + return &req; +} + +// Complete a request and return OK (or SIGNED_OUT for async when disabled). +static s32 CompleteRequest(NpRequest& req, s32 result) { + req.state = NpRequestState::Complete; + req.result = result; + if (result != ORBIS_OK && req.async) { + // Async requests always return OK immediately; result is read via PollAsync/WaitAsync. + return ORBIS_OK; + } + return result; +} + s32 PS4_SYSV_ABI sceNpCreateRequest() { LOG_DEBUG(Lib_NpManager, "called"); return CreateNpRequest(false); @@ -87,353 +206,14 @@ s32 PS4_SYSV_ABI sceNpCreateAsyncRequest(const OrbisNpCreateAsyncRequestParamete return CreateNpRequest(true); } -s32 PS4_SYSV_ABI sceNpCheckNpAvailability(s32 req_id, OrbisNpOnlineId* online_id) { - if (online_id == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id, - Libraries::UserService::OrbisUserServiceUserId user_id) { - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpCheckNpReachability(s32 req_id, - Libraries::UserService::OrbisUserServiceUserId user_id) { - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpCheckPlus(s32 req_id, const OrbisNpCheckPlusParameter* param, - OrbisNpCheckPlusResult* result) { - - if (req_id == 0 || param == nullptr || result == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - if (param->size != sizeof(OrbisNpCheckPlusParameter)) { - return ORBIS_NP_ERROR_INVALID_SIZE; - } - - if (param->features < 1 || param->features > 3) { - // TODO: If compiled SDK version is greater or equal to fw 3.50, - // error if param->features != 1 instead. - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, - "(STUBBED) called, req_id = {:#x}, is_async = {}, param.features = {:#x}", req_id, - request.async, param->features); - - // For now, set authorized to true to signal PS+ access. - result->authorized = true; - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpGetAccountLanguage(s32 req_id, OrbisNpOnlineId* online_id, - OrbisNpLanguageCode* language) { - if (online_id == nullptr || language == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - std::memset(language, 0, sizeof(OrbisNpLanguageCode)); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpGetAccountLanguageA(s32 req_id, - Libraries::UserService::OrbisUserServiceUserId user_id, - OrbisNpLanguageCode* language) { - if (language == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, user_id = {}, is_async = {}", - req_id, user_id, request.async); - - std::memset(language, 0, sizeof(OrbisNpLanguageCode)); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceNpGetParentalControlInfo(s32 req_id, OrbisNpOnlineId* online_id, s8* age, - OrbisNpParentalControlInfo* info) { - if (online_id == nullptr || age == nullptr || info == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - // TODO: Add to config? - *age = 13; - std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI -sceNpGetParentalControlInfoA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id, - s8* age, OrbisNpParentalControlInfo* info) { - if (age == nullptr || info == nullptr) { - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, user_id = {}, is_async = {}", - req_id, user_id, request.async); - - // TODO: Add to config? - *age = 13; - std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); - - request.result = ORBIS_OK; - return ORBIS_OK; -} - s32 PS4_SYSV_ABI sceNpAbortRequest(s32 req_id) { LOG_DEBUG(Lib_NpManager, "called req_id = {:#x}", req_id); std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -447,7 +227,7 @@ s32 PS4_SYSV_ABI sceNpAbortRequest(s32 req_id) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpWaitAsync(s32 req_id, s32* result) { +s32 PS4_SYSV_ABI sceNpPollAsync(s32 req_id, s32* result) { if (result == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } @@ -455,7 +235,8 @@ s32 PS4_SYSV_ABI sceNpWaitAsync(s32 req_id, s32* result) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -472,7 +253,7 @@ s32 PS4_SYSV_ABI sceNpWaitAsync(s32 req_id, s32* result) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpPollAsync(s32 req_id, s32* result) { +s32 PS4_SYSV_ABI sceNpWaitAsync(s32 req_id, s32* result) { if (result == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } @@ -480,7 +261,8 @@ s32 PS4_SYSV_ABI sceNpPollAsync(s32 req_id, s32* result) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -503,7 +285,8 @@ s32 PS4_SYSV_ABI sceNpDeleteRequest(s32 req_id) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -513,17 +296,177 @@ s32 PS4_SYSV_ABI sceNpDeleteRequest(s32 req_id) { return ORBIS_OK; } +// Availabity and reachability checks. +s32 PS4_SYSV_ABI sceNpCheckNpAvailability(s32 req_id, OrbisNpOnlineId* online_id) { + if (online_id == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}", req_id); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id, + Libraries::UserService::OrbisUserServiceUserId user_id) { + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + LOG_ERROR(Lib_NpManager, "invalid user_id {}", user_id); + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI sceNpCheckNpReachability(s32 req_id, + Libraries::UserService::OrbisUserServiceUserId user_id) { + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI sceNpCheckPlus(s32 req_id, const OrbisNpCheckPlusParameter* param, + OrbisNpCheckPlusResult* result) { + if (req_id == 0 || param == nullptr || result == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + if (param->size != sizeof(OrbisNpCheckPlusParameter)) { + return ORBIS_NP_ERROR_INVALID_SIZE; + } + if (param->features < 1 || param->features > 3) { + // TODO: If compiled SDK version is greater or equal to fw 3.50, + // // error if param->features != 1 instead. + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(param->user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, features = {:#x}", req_id, param->features); + // Grant PS+ — shadNet has no subscription gating. + result->authorized = true; + return CompleteRequest(*req, ORBIS_OK); +} + +// Account info +s32 PS4_SYSV_ABI sceNpGetAccountLanguage(s32 req_id, OrbisNpOnlineId* online_id, + OrbisNpLanguageCode* language) { + if (online_id == nullptr || language == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}", req_id); + FillLanguageCodeFromProfile(user_id, language); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI sceNpGetAccountLanguageA(s32 req_id, + Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpLanguageCode* language) { + if (language == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + FillLanguageCodeFromProfile(user_id, language); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI sceNpGetParentalControlInfo(s32 req_id, OrbisNpOnlineId* online_id, s8* age, + OrbisNpParentalControlInfo* info) { + if (online_id == nullptr || age == nullptr || info == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}", req_id); + *age = GetAgeFromProfile(user_id); + std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); + return CompleteRequest(*req, ORBIS_OK); +} + +s32 PS4_SYSV_ABI +sceNpGetParentalControlInfoA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id, + s8* age, OrbisNpParentalControlInfo* info) { + if (age == nullptr || info == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + *age = GetAgeFromProfile(user_id); + std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); + return CompleteRequest(*req, ORBIS_OK); +} + s32 PS4_SYSV_ABI sceNpGetAccountCountry(OrbisNpOnlineId* online_id, OrbisNpCountryCode* country_code) { if (online_id == nullptr || country_code == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - std::memset(country_code, 0, sizeof(OrbisNpCountryCode)); - // TODO: get NP country code from config - std::memcpy(country_code->country_code, "us", 2); + FillCountryCodeFromProfile(user_id, country_code); return ORBIS_OK; } @@ -532,12 +475,10 @@ s32 PS4_SYSV_ABI sceNpGetAccountCountryA(Libraries::UserService::OrbisUserServic if (country_code == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - std::memset(country_code, 0, sizeof(OrbisNpCountryCode)); - // TODO: get NP country code from config - std::memcpy(country_code->country_code, "us", 2); + FillCountryCodeFromProfile(user_id, country_code); return ORBIS_OK; } @@ -546,14 +487,12 @@ s32 PS4_SYSV_ABI sceNpGetAccountDateOfBirth(OrbisNpOnlineId* online_id, if (online_id == nullptr || date_of_birth == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - - // TODO: maybe add to config? - date_of_birth->day = 1; - date_of_birth->month = 1; - date_of_birth->year = 2000; + FillDateOfBirthFromProfile(user_id, date_of_birth); return ORBIS_OK; } @@ -562,14 +501,10 @@ s32 PS4_SYSV_ABI sceNpGetAccountDateOfBirthA(Libraries::UserService::OrbisUserSe if (date_of_birth == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - - // TODO: maybe add to config? - date_of_birth->day = 1; - date_of_birth->month = 1; - date_of_birth->year = 2000; + FillDateOfBirthFromProfile(user_id, date_of_birth); return ORBIS_OK; } @@ -578,9 +513,11 @@ s32 PS4_SYSV_ABI sceNpGetGamePresenceStatus(OrbisNpOnlineId* online_id, if (online_id == nullptr || game_status == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - *game_status = - g_shadnet_enabled ? OrbisNpGamePresenseStatus::Online : OrbisNpGamePresenseStatus::Offline; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + *game_status = (g_shadnet_enabled && user_id != -1 && + Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpGamePresenseStatus::Online + : OrbisNpGamePresenseStatus::Offline; return ORBIS_OK; } @@ -589,9 +526,10 @@ s32 PS4_SYSV_ABI sceNpGetGamePresenceStatusA(Libraries::UserService::OrbisUserSe if (game_status == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - *game_status = - g_shadnet_enabled ? OrbisNpGamePresenseStatus::Online : OrbisNpGamePresenseStatus::Offline; + (g_shadnet_enabled && Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpGamePresenseStatus::Online + : OrbisNpGamePresenseStatus::Offline; return ORBIS_OK; } @@ -600,11 +538,13 @@ s32 PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id) if (online_id == nullptr || account_id == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (!g_shadnet_enabled || user_id == -1 || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { *account_id = 0; return ORBIS_NP_ERROR_SIGNED_OUT; } - *account_id = 0xFEEDFACE; + *account_id = Libraries::Np::NpHandler::GetInstance().GetAccountId(user_id); return ORBIS_OK; } @@ -614,52 +554,75 @@ s32 PS4_SYSV_ABI sceNpGetAccountIdA(Libraries::UserService::OrbisUserServiceUser if (account_id == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { *account_id = 0; return ORBIS_NP_ERROR_SIGNED_OUT; } - *account_id = 0xFEEDFACE; + *account_id = Libraries::Np::NpHandler::GetInstance().GetAccountId(user_id); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpId* np_id) { LOG_DEBUG(Lib_NpManager, "user_id {}", user_id); + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return (g_firmware_version >= 0 && g_firmware_version < Common::ElfInfo::FW_90) + ? ORBIS_NP_ERROR_USER_NOT_FOUND + : ORBIS_NP_ERROR_INVALID_ARGUMENT; + } if (np_id == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + LOG_WARNING(Lib_NpManager, + "sceNpGetNpId: SIGNED_OUT (user_id={} shadnet_enabled={} signed_in={})", + user_id, g_shadnet_enabled, + Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)); return ORBIS_NP_ERROR_SIGNED_OUT; } - memset(np_id, 0, sizeof(OrbisNpId)); - strncpy(np_id->handle.data, UserManagement.GetDefaultUser().user_name.c_str(), - sizeof(np_id->handle.data)); + *np_id = Libraries::Np::NpHandler::GetInstance().GetNpId(user_id); + LOG_INFO(Lib_NpManager, "sceNpGetNpId: user_id={} handle.data='{}' (strnlen={})", user_id, + np_id->handle.data, strnlen(np_id->handle.data, sizeof(np_id->handle.data))); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id) { LOG_DEBUG(Lib_NpManager, "user_id {}", user_id); + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return (g_firmware_version >= 0 && g_firmware_version < Common::ElfInfo::FW_90) + ? ORBIS_NP_ERROR_USER_NOT_FOUND + : ORBIS_NP_ERROR_INVALID_ARGUMENT; + } if (online_id == nullptr) { + LOG_ERROR(Lib_NpManager, "invalid argument: online_id is null"); return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + LOG_INFO(Lib_NpManager, + "sceNpGetOnlineId: SIGNED_OUT (user_id={} shadnet_enabled={} signed_in={})", + user_id, g_shadnet_enabled, + Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)); return ORBIS_NP_ERROR_SIGNED_OUT; } - memset(online_id, 0, sizeof(OrbisNpOnlineId)); - strncpy(online_id->data, UserManagement.GetDefaultUser().user_name.c_str(), - sizeof(online_id->data)); + *online_id = Libraries::Np::NpHandler::GetInstance().GetOnlineId(user_id); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetNpReachabilityState(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpReachabilityState* state) { if (state == nullptr) { + LOG_ERROR(Lib_NpManager, "invalid argument: state is null"); return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - *state = g_shadnet_enabled ? OrbisNpReachabilityState::Reachable - : OrbisNpReachabilityState::Unavailable; + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_40) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + *state = (g_shadnet_enabled && Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpReachabilityState::Reachable + : OrbisNpReachabilityState::Unavailable; return ORBIS_OK; } @@ -668,57 +631,78 @@ s32 PS4_SYSV_ABI sceNpGetState(Libraries::UserService::OrbisUserServiceUserId us if (state == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - *state = g_shadnet_enabled ? OrbisNpState::SignedIn : OrbisNpState::SignedOut; - LOG_DEBUG(Lib_NpManager, "Signed {}", g_shadnet_enabled ? "in" : "out"); + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_90) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + if (!g_shadnet_enabled) { + *state = OrbisNpState::SignedOut; + LOG_DEBUG(Lib_NpManager, "shadNet disabled,SignedOut"); + return ORBIS_OK; + } + *state = Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id) + ? OrbisNpState::SignedIn + : OrbisNpState::SignedOut; + LOG_DEBUG(Lib_NpManager, "user_id={} state={}", user_id, + *state == OrbisNpState::SignedIn ? "SignedIn" : "SignedOut"); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServiceUserId* user_id) { - if (user_id == nullptr) { + if (account_id == 0 || user_id == nullptr) { + LOG_ERROR(Lib_NpManager, "invalid argument: account_id={}", account_id); return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (!g_shadnet_enabled) return ORBIS_NP_ERROR_SIGNED_OUT; - } - *user_id = 1; - LOG_DEBUG(Lib_NpManager, "userid({}) = {}", account_id, *user_id); + + const s32 found = Libraries::Np::NpHandler::GetInstance().GetUserIdByAccountId(account_id); + if (found == -1) + return ORBIS_NP_ERROR_SIGNED_OUT; + + // Verify the resolved user_id is actually a logged-in local user + const User* u = UserManagement.GetUserByID(found); + if (!u || !u->logged_in) + return ORBIS_NP_ERROR_USER_NOT_FOUND; + + *user_id = found; + LOG_DEBUG(Lib_NpManager, "account_id={} returns user_id={}", account_id, *user_id); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId user_id, bool* has_signed_up) { - LOG_DEBUG(Lib_NpManager, "called"); if (has_signed_up == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - *has_signed_up = g_shadnet_enabled ? true : false; + *has_signed_up = false; + + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_90) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + + const User* u = UserManagement.GetUserByID(user_id); + if (!u) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } + // A user has signed up if they have a shadNet npid configured. + // This is independent of shadnet_enabled and current connection state. + *has_signed_up = !u->shadnet_npid.empty(); return ORBIS_OK; } -struct NpStateCallbackForNpToolkit { - OrbisNpStateCallbackForNpToolkit func; - void* userdata; -}; - -NpStateCallbackForNpToolkit NpStateCbForNp; - -struct NpStateCallback { - std::variant func; - void* userdata; -}; - -NpStateCallback NpStateCb; +// Callbacks s32 PS4_SYSV_ABI sceNpCheckCallback() { - LOG_DEBUG(Lib_NpManager, "(STUBBED) called"); - + LOG_DEBUG(Lib_NpManager, "called"); std::scoped_lock lk{g_np_callbacks_mutex}; - - for (auto i : g_np_callbacks) { - (i.second)(); + for (auto& [key, cb] : g_np_callbacks) { + cb(); } - return ORBIS_OK; } @@ -728,66 +712,117 @@ s32 PS4_SYSV_ABI sceNpCheckCallbackForLib() { } s32 PS4_SYSV_ABI sceNpRegisterStateCallback(OrbisNpStateCallback callback, void* userdata) { - static s32 id = 0; - LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata); + LOG_DEBUG(Lib_NpManager, "called"); NpStateCb.func = callback; NpStateCb.userdata = userdata; - - return id; + // Register with NpHandler so it fires on live state changes. + // The non-A variant additionally passes an OrbisNpId* built from the user's + // shadnet_npid or nullptr when signing out. + return Libraries::Np::NpHandler::GetInstance().RegisterStateCallback( + [callback, userdata](Libraries::UserService::OrbisUserServiceUserId uid, + Libraries::Np::NpManager::OrbisNpState state) { + // Use NpHandler's cached NpId — built from shadnet_npid at login. + const OrbisNpId& cached = Libraries::Np::NpHandler::GetInstance().GetNpId(uid); + OrbisNpId* np_id_ptr = + (state == OrbisNpState::SignedIn && cached.handle.data[0] != '\0') + ? const_cast(&cached) + : nullptr; + callback(uid, state, np_id_ptr, userdata); + }, + userdata); } s32 PS4_SYSV_ABI sceNpRegisterStateCallbackA(OrbisNpStateCallbackA callback, void* userdata) { - static s32 id = 0; - LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata); + LOG_DEBUG(Lib_NpManager, "called"); + if (callback == nullptr) { + LOG_ERROR(Lib_NpManager, "callback is nullptr"); + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + if (std::holds_alternative(NpStateCb.func) && + std::get(NpStateCb.func) != nullptr) { + LOG_ERROR(Lib_NpManager, "Callback already registered, cannot register multiple"); + return ORBIS_NP_ERROR_CALLBACK_ALREADY_REGISTERED; + } NpStateCb.func = callback; NpStateCb.userdata = userdata; - - return id; + // Register with NpHandler so the callback fires on live connection state changes. + return Libraries::Np::NpHandler::GetInstance().RegisterStateCallback( + [callback, userdata](Libraries::UserService::OrbisUserServiceUserId uid, + Libraries::Np::NpManager::OrbisNpState state) { + callback(uid, state, userdata); + }, + userdata); } -struct NpReachabilityStateCallback { - OrbisNpReachabilityStateCallback func; - void* userdata; -}; +s32 PS4_SYSV_ABI sceNpUnregisterStateCallbackA(s32 callback_id) { + if (callback_id <= 0) { + LOG_ERROR(Lib_NpManager, "invalid callback_id {}", callback_id); + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } -NpReachabilityStateCallback NpReachabilityCb; + // Check if this handle was actually registered before unregistering + if (!std::holds_alternative(NpStateCb.func) || + std::get(NpStateCb.func) == nullptr) { + LOG_ERROR(Lib_NpManager, "callback with id {} not registered", callback_id); + return ORBIS_NP_ERROR_CALLBACK_NOT_REGISTERED; + } + Libraries::Np::NpHandler::GetInstance().UnregisterStateCallback(callback_id); + NpStateCb.func = OrbisNpStateCallbackA{}; + NpStateCb.userdata = nullptr; + return ORBIS_OK; +} s32 PS4_SYSV_ABI sceNpRegisterNpReachabilityStateCallback(OrbisNpReachabilityStateCallback callback, void* userdata) { - static s32 id = 0; + if (callback == nullptr) { + LOG_ERROR(Lib_NpManager, "callback is nullptr"); + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + if (NpReachabilityCb.func != nullptr) { + LOG_ERROR(Lib_NpManager, "callback already registered, cannot register multiple"); + return ORBIS_NP_ERROR_CALLBACK_ALREADY_REGISTERED; + } LOG_ERROR(Lib_NpManager, "(STUBBED) called"); NpReachabilityCb.func = callback; NpReachabilityCb.userdata = userdata; - return id; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpUnregisterNpReachabilityStateCallback() { + if (NpReachabilityCb.func == nullptr) { + LOG_ERROR(Lib_NpManager, "callback not registered"); + return ORBIS_NP_ERROR_CALLBACK_NOT_REGISTERED; + } + NpReachabilityCb.func = nullptr; + NpReachabilityCb.userdata = nullptr; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback, void* userdata) { - static s32 id = 0; LOG_ERROR(Lib_NpManager, "(STUBBED) called"); NpStateCbForNp.func = callback; NpStateCbForNp.userdata = userdata; - return id; + return ORBIS_OK; } void RegisterNpCallback(std::string key, std::function cb) { std::scoped_lock lk{g_np_callbacks_mutex}; - LOG_DEBUG(Lib_NpManager, "registering callback processing for {}", key); - g_np_callbacks.emplace(key, cb); } void DeregisterNpCallback(std::string key) { std::scoped_lock lk{g_np_callbacks_mutex}; - LOG_DEBUG(Lib_NpManager, "deregistering callback processing for {}", key); - g_np_callbacks.erase(key); } void RegisterLib(Core::Loader::SymbolsResolver* sym) { + ASSERT_MSG(Libraries::Kernel::sceKernelGetCompiledSdkVersion(&g_firmware_version) == ORBIS_OK, + "Failed to get compiled SDK verision."); g_shadnet_enabled = EmulatorSettings.IsShadNetEnabled(); + Libraries::Np::NpHandler::GetInstance().Initialize(); LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest); LIB_FUNCTION("eiqMCt9UshI", "libSceNpManager", 1, "libSceNpManager", sceNpCreateAsyncRequest); @@ -830,8 +865,14 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib); LIB_FUNCTION("VfRSmPmj8Q8", "libSceNpManager", 1, "libSceNpManager", sceNpRegisterStateCallback); + LIB_FUNCTION("qQJfO8HAiaY", "libSceNpManager", 1, "libSceNpManager", + sceNpRegisterStateCallbackA); + LIB_FUNCTION("M3wFXbYQtAA", "libSceNpManager", 1, "libSceNpManager", + sceNpUnregisterStateCallbackA); LIB_FUNCTION("hw5KNqAAels", "libSceNpManager", 1, "libSceNpManager", sceNpRegisterNpReachabilityStateCallback); + LIB_FUNCTION("cRILAEvn+9M", "libSceNpManager", 1, "libSceNpManager", + sceNpUnregisterNpReachabilityStateCallback); LIB_FUNCTION("JELHf4xPufo", "libSceNpManagerForToolkit", 1, "libSceNpManager", sceNpCheckCallbackForLib); LIB_FUNCTION("0c7HbXRKUt4", "libSceNpManagerForToolkit", 1, "libSceNpManager", diff --git a/src/core/libraries/np/np_party.cpp b/src/core/libraries/np/np_party.cpp index 076677607..ba44bcde7 100644 --- a/src/core/libraries/np/np_party.cpp +++ b/src/core/libraries/np/np_party.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -6,7 +6,6 @@ #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_party.h" -#include "core/libraries/np/np_party_error.h" namespace Libraries::Np::NpParty { diff --git a/src/core/libraries/np/np_party_error.h b/src/core/libraries/np/np_party_error.h deleted file mode 100644 index 7f132e442..000000000 --- a/src/core/libraries/np/np_party_error.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/error_codes.h" - -constexpr int ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY = 0x80552506; \ No newline at end of file diff --git a/src/core/libraries/np/np_score.cpp b/src/core/libraries/np/np_score.cpp deleted file mode 100644 index 1c4fbe015..000000000 --- a/src/core/libraries/np/np_score.cpp +++ /dev/null @@ -1,646 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/np/np_score.h" - -namespace Libraries::Np::NpScore { - -// Helper macro to format pointer safely -#define PTR(ptr) static_cast(ptr) - -int PS4_SYSV_ABI sceNpScoreAbortRequest(s32 reqId) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}", reqId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCensorComment(s32 reqId, const char* comment, void* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, comment={}, option={}", reqId, - comment ? comment : "null", PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCensorCommentAsync(s32 reqId, const char* comment, void* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, comment={}, option={}", reqId, - comment ? comment : "null", PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId) { - LOG_ERROR(Lib_NpScore, "serviceLabel = {}, npId->data = {}", serviceLabel, npId->handle.data); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCreateRequest(s32 titleCtxId) { - LOG_ERROR(Lib_NpScore, "libCtxId = {}", titleCtxId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreDeleteRequest(s32 reqId) { - LOG_ERROR(Lib_NpScore, "requestId = {:#x}", reqId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA(OrbisNpServiceLabel npServiceLabel, - UserService::OrbisUserServiceUserId selfId) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called npServiceLabel={}, selfId={}", - static_cast(npServiceLabel), selfId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreCreateTitleCtx() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(s32 titleCtxId) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called titleCtxId={}", titleCtxId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetBoardInfo(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreBoardInfo* boardInfo, void* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, boardId={}, boardInfo={}, option={}", reqId, - boardId, PTR(boardInfo), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreBoardInfo* boardInfo, void* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, boardId={}, boardInfo={}, option={}", reqId, - boardId, PTR(boardInfo), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRanking(s32 reqId, OrbisNpScoreBoardId boardId, - s32 includeSelf, OrbisNpScoreRankData* rankArray, - u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, includeSelf={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRange( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, "called reqId={}, boardId={}, startSerialRank={}, arrayNum={}", reqId, - boardId, startSerialRank, arrayNum); - - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingA(s32 reqId, OrbisNpScoreBoardId boardId, - s32 includeSelf, OrbisNpScoreRankDataA* rankArray, - u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, - OrbisNpScoreGetFriendRankingOptParam* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, includeSelf={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAAsync( - s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankDataA* rankArray, - u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, - OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, - OrbisNpScoreGetFriendRankingOptParam* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, includeSelf={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync( - s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankData* rankArray, - u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, - OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, - OrbisNpScoreGetFriendRankingOptParam* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSave( - s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, - OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, includeSelf={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSaveAsync( - s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, - OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option) { - LOG_ERROR( - Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "includeSelf={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " - "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetGameData() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetGameDataAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountId(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpAccountId accountId, u64* totalSize, - u64 recvSize, void* data, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, accountId={}, " - "totalSize={}, recvSize={}, data={}, option={}", - reqId, boardId, accountId, PTR(totalSize), recvSize, PTR(data), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountIdAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpAccountId accountId, u64* totalSize, - u64 recvSize, void* data, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, accountId={}, " - "totalSize={}, recvSize={}, data={}, option={}", - reqId, boardId, accountId, PTR(totalSize), recvSize, PTR(data), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountId( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, accountIdArray={}, " - "accountIdArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " - "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " - "totalRecord={}, option={}", - reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), - rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, - arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " - "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " - "lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), - rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, - arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSave( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " - "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " - "lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), - rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, - arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " - "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " - "lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), - rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, - arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcId( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR( - Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, idArray={}, " - "idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " - "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR( - Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, idArray={}, " - "idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " - "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "idArray={}, idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " - "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " - "totalRecord={}, option={}", - reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, - PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, - PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "idArray={}, idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " - "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " - "totalRecord={}, option={}", - reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, - PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, - PTR(lastSortDate), PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByNpId() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync() { - LOG_ERROR(Lib_NpScore, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, startSerialRank={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, startSerialRank={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, startSerialRank={}, " - "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " - "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR( - Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "startSerialRank={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " - "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSaveAsync( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option) { - LOG_ERROR( - Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, " - "startSerialRank={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " - "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", - reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), - commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), - PTR(totalRecord), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScorePollAsync(s32 reqId, s32* result) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, result={}", reqId, PTR(result)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreRecordGameData(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, u64 totalSize, u64 sendSize, - const void* data, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, score={}, totalSize={}, " - "sendSize={}, data={}, option={}", - reqId, boardId, score, totalSize, sendSize, PTR(data), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreRecordGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, u64 totalSize, u64 sendSize, - const void* data, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, score={}, " - "totalSize={}, sendSize={}, data={}, option={}", - reqId, boardId, score, totalSize, sendSize, PTR(data), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreRecordScore(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, - const OrbisNpScoreComment* scoreComment, - const OrbisNpScoreGameInfo* gameInfo, - OrbisNpScoreRankNumber* tmpRank, - const Rtc::OrbisRtcTick* compareDate, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, score={}, scoreComment={}, " - "gameInfo={}, tmpRank={}, compareDate={}, option={}", - reqId, boardId, score, PTR(scoreComment), PTR(gameInfo), PTR(tmpRank), - PTR(compareDate), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreRecordScoreAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, - const OrbisNpScoreComment* scoreComment, - const OrbisNpScoreGameInfo* gameInfo, - OrbisNpScoreRankNumber* tmpRank, - const Rtc::OrbisRtcTick* compareDate, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, boardId={}, score={}, " - "scoreComment={}, gameInfo={}, tmpRank={}, compareDate={}, option={}", - reqId, boardId, score, PTR(scoreComment), PTR(gameInfo), PTR(tmpRank), - PTR(compareDate), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreSanitizeComment(s32 reqId, const char* comment, char* sanitizedComment, - void* option) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called reqId={}, comment={}, sanitizedComment={}, option={}", - reqId, comment ? comment : "null", PTR(sanitizedComment), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync(s32 reqId, const char* comment, - char* sanitizedComment, void* option) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called reqId={}, comment={}, sanitizedComment={}, " - "option={}", - reqId, comment ? comment : "null", PTR(sanitizedComment), PTR(option)); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(s32 ctxId, OrbisNpScorePcId pcId) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called ctxId={}, pcId={}", ctxId, pcId); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreSetThreadParam(s32 threadPriority, u64 cpuAffinityMask) { - LOG_ERROR(Lib_NpScore, "(STUBBED) called threadPriority={}, cpuAffinityMask={:#x}", - threadPriority, cpuAffinityMask); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreSetTimeout(s32 id, s32 resolveRetry, s32 resolveTimeout, s32 connTimeout, - s32 sendTimeout, s32 recvTimeout) { - LOG_ERROR(Lib_NpScore, - "(STUBBED) called id={}, resolveRetry={}, resolveTimeout={}, " - "connTimeout={}, sendTimeout={}, recvTimeout={}", - id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceNpScoreWaitAsync(s32 reqId, s32* result) { - LOG_ERROR(Lib_NpScore, "(STUBBED) sceNpScoreWaitAsync(reqId={}, result={})", reqId, - PTR(result)); - return ORBIS_OK; -} - -void RegisterLib(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("1i7kmKbX6hk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreAbortRequest); - LIB_FUNCTION("2b3TI0mDYiI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCensorComment); - LIB_FUNCTION("4eOvDyN-aZc", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCensorCommentAsync); - LIB_FUNCTION("dTXC+YcePtM", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreChangeModeForOtherSaveDataOwners); - LIB_FUNCTION("KnNA1TEgtBI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateNpTitleCtx); - LIB_FUNCTION("GWnWQNXZH5M", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateNpTitleCtxA); - LIB_FUNCTION("gW8qyjYrUbk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateRequest); - LIB_FUNCTION("qW9M0bQ-Zx0", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateTitleCtx); - LIB_FUNCTION("G0pE+RNCwfk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreDeleteNpTitleCtx); - LIB_FUNCTION("dK8-SgYf6r4", "libSceNpScore", 1, "libSceNpScore", sceNpScoreDeleteRequest); - LIB_FUNCTION("LoVMVrijVOk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetBoardInfo); - LIB_FUNCTION("Q0Avi9kebsY", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetBoardInfoAsync); - LIB_FUNCTION("8kuIzUw6utQ", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetFriendsRanking); - LIB_FUNCTION("gMbOn+-6eXA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetFriendsRankingA); - LIB_FUNCTION("6-G9OxL5DKg", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetFriendsRankingAAsync); - LIB_FUNCTION("7SuMUlN7Q6I", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetFriendsRankingAsync); - LIB_FUNCTION("AgcxgceaH8k", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetFriendsRankingForCrossSave); - LIB_FUNCTION("m6F7sE1HQZU", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetFriendsRankingForCrossSaveAsync); - LIB_FUNCTION("zKoVok6FFEI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetGameData); - LIB_FUNCTION("JjOFRVPdQWc", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetGameDataAsync); - LIB_FUNCTION("Lmtc9GljeUA", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetGameDataByAccountId); - LIB_FUNCTION("PP9jx8s0574", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetGameDataByAccountIdAsync); - LIB_FUNCTION("K9tlODTQx3c", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountId); - LIB_FUNCTION("dRszNNyGWkw", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdAsync); - LIB_FUNCTION("3Ybj4E1qNtY", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdForCrossSave); - LIB_FUNCTION("Kc+3QK84AKM", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdForCrossSaveAsync); - LIB_FUNCTION("wJPWycVGzrs", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdPcId); - LIB_FUNCTION("bFVjDgxFapc", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdPcIdAsync); - LIB_FUNCTION("oXjVieH6ZGQ", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdPcIdForCrossSave); - LIB_FUNCTION("nXaF1Bxb-Nw", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync); - LIB_FUNCTION("9mZEgoiEq6Y", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByNpId); - LIB_FUNCTION("Rd27dqUFZV8", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdAsync); - LIB_FUNCTION("ETS-uM-vH9Q", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdPcId); - LIB_FUNCTION("FsouSN0ykN8", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdPcIdAsync); - LIB_FUNCTION("KBHxDjyk-jA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByRange); - LIB_FUNCTION("MA9vSt7JImY", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByRangeA); - LIB_FUNCTION("y5ja7WI05rs", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByRangeAAsync); - LIB_FUNCTION("rShmqXHwoQE", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByRangeAsync); - LIB_FUNCTION("nRoYV2yeUuw", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByRangeForCrossSave); - LIB_FUNCTION("AZ4eAlGDy-Q", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreGetRankingByRangeForCrossSaveAsync); - LIB_FUNCTION("m1DfNRstkSQ", "libSceNpScore", 1, "libSceNpScore", sceNpScorePollAsync); - LIB_FUNCTION("bcoVwcBjQ9E", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordGameData); - LIB_FUNCTION("1gL5PwYzrrw", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordGameDataAsync); - LIB_FUNCTION("zT0XBtgtOSI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordScore); - LIB_FUNCTION("ANJssPz3mY0", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordScoreAsync); - LIB_FUNCTION("r4oAo9in0TA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSanitizeComment); - LIB_FUNCTION("3UVqGJeDf30", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreSanitizeCommentAsync); - LIB_FUNCTION("bygbKdHmjn4", "libSceNpScore", 1, "libSceNpScore", - sceNpScoreSetPlayerCharacterId); - LIB_FUNCTION("yxK68584JAU", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSetThreadParam); - LIB_FUNCTION("S3xZj35v8Z8", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSetTimeout); - LIB_FUNCTION("fqk8SC63p1U", "libSceNpScore", 1, "libSceNpScore", sceNpScoreWaitAsync); - LIB_FUNCTION("KnNA1TEgtBI", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreCreateNpTitleCtx); - LIB_FUNCTION("8kuIzUw6utQ", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetFriendsRanking); - LIB_FUNCTION("7SuMUlN7Q6I", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetFriendsRankingAsync); - LIB_FUNCTION("zKoVok6FFEI", "libSceNpScoreCompat", 1, "libSceNpScore", sceNpScoreGetGameData); - LIB_FUNCTION("JjOFRVPdQWc", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetGameDataAsync); - LIB_FUNCTION("9mZEgoiEq6Y", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByNpId); - LIB_FUNCTION("Rd27dqUFZV8", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdAsync); - LIB_FUNCTION("ETS-uM-vH9Q", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdPcId); - LIB_FUNCTION("FsouSN0ykN8", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByNpIdPcIdAsync); - LIB_FUNCTION("KBHxDjyk-jA", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByRange); - LIB_FUNCTION("rShmqXHwoQE", "libSceNpScoreCompat", 1, "libSceNpScore", - sceNpScoreGetRankingByRangeAsync); -}; - -} // namespace Libraries::Np::NpScore \ No newline at end of file diff --git a/src/core/libraries/np/np_score/np_score.cpp b/src/core/libraries/np/np_score/np_score.cpp new file mode 100644 index 000000000..96326b28e --- /dev/null +++ b/src/core/libraries/np/np_score/np_score.cpp @@ -0,0 +1,1845 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/process.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_error.h" +#include "core/libraries/np/np_handler.h" +#include "core/libraries/np/np_score/np_score.h" +#include "core/libraries/np/np_score/np_score_ctx.h" + +namespace Libraries::Np::NpScore { + +// Helper macro to format pointer safely +#define PTR(ptr) static_cast(ptr) + +struct ScoreTitleCtx { + OrbisNpServiceLabel serviceLabel = 0; + s32 userId = -1; + OrbisNpScorePcId pcId = 0; +}; + +static std::mutex g_mutex; +static std::map g_title_ctxs; +static std::map> g_requests; +static OrbisNpScoreTitleCtxId g_next_ctx_id = 1; +static OrbisNpScoreRequestId g_next_req_id = 1; +static s32 g_firmware_version = -1; + +// Internal helpers +static ScoreTitleCtx* LookupTitleCtxUnlocked(OrbisNpScoreTitleCtxId id) { + auto it = g_title_ctxs.find(id); + return it == g_title_ctxs.end() ? nullptr : &it->second; +} + +static std::shared_ptr LookupRequestUnlocked(OrbisNpScoreRequestId id) { + auto it = g_requests.find(id); + return it == g_requests.end() ? nullptr : it->second; +} + +static bool IsRequestAborted(const std::shared_ptr& req) { + std::lock_guard lock(req->mutex); + return req->result.has_value() && *req->result == ORBIS_NP_COMMUNITY_ERROR_ABORTED; +} + +static s32 ServiceLabelForRequest(const std::shared_ptr& req) { + if (!req) + return 0; + std::lock_guard lock(g_mutex); + auto* tc = LookupTitleCtxUnlocked(req->titleCtxId); + return tc != nullptr ? tc->serviceLabel : 0; +} + +//*********************************** +// Title context management functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, + const OrbisNpId* selfNpId) { + if (serviceLabel == static_cast(ORBIS_NP_INVALID_SERVICE_LABEL)) { + LOG_ERROR(Lib_NpScore, "invalid serviceLabel"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (!selfNpId) { + LOG_ERROR(Lib_NpScore, "selfNpId is null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + std::lock_guard lock(g_mutex); + if (static_cast(g_title_ctxs.size()) >= ORBIS_NP_SCORE_MAX_CTX_NUM) { + LOG_ERROR(Lib_NpScore, "Too many title contexts already exist ({})", g_title_ctxs.size()); + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + } + const s32 userId = + Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(selfNpId->handle); + const OrbisNpScoreTitleCtxId id = g_next_ctx_id++; + g_title_ctxs[id] = ScoreTitleCtx{.serviceLabel = serviceLabel, .userId = userId}; + LOG_INFO(Lib_NpScore, "CreateNpTitleCtx id={} serviceLabel={} userId={}", id, serviceLabel, + userId); + return id; +} + +s32 PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA(OrbisNpServiceLabel npServiceLabel, + UserService::OrbisUserServiceUserId selfId) { + + if (!Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(selfId)) { + LOG_ERROR(Lib_NpScore, "userId {} is not signed in to NP", selfId); + return ORBIS_NP_ERROR_SIGNED_OUT; + } + if (npServiceLabel == static_cast(ORBIS_NP_INVALID_SERVICE_LABEL)) { + LOG_ERROR(Lib_NpScore, "invalid serviceLabel"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + std::lock_guard lock(g_mutex); + if (static_cast(g_title_ctxs.size()) >= ORBIS_NP_SCORE_MAX_CTX_NUM) { + LOG_ERROR(Lib_NpScore, "Too many title contexts already exist ({})", g_title_ctxs.size()); + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + } + const OrbisNpScoreTitleCtxId id = g_next_ctx_id++; + g_title_ctxs[id] = ScoreTitleCtx{.serviceLabel = npServiceLabel, .userId = selfId}; + LOG_INFO(Lib_NpScore, "CreateNpTitleCtxA id={} serviceLabel={} userId={}", id, npServiceLabel, + selfId); + return id; +} + +s32 PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(s32 titleCtxId) { + std::lock_guard lock(g_mutex); + if (!g_title_ctxs.contains(titleCtxId)) { + LOG_ERROR(Lib_NpScore, "invalid titleCtxId {}", titleCtxId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + + for (auto it = g_requests.begin(); it != g_requests.end();) { + if (it->second->titleCtxId == titleCtxId) { + it->second->SetResult(ORBIS_NP_COMMUNITY_ERROR_ABORTED); + it = g_requests.erase(it); + } else { + ++it; + } + } + g_title_ctxs.erase(titleCtxId); + LOG_INFO(Lib_NpScore, "DeleteNpTitleCtx id={}", titleCtxId); + return ORBIS_OK; +} + +//*********************************** +// Request management functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreCreateRequest(s32 titleCtxId) { + std::lock_guard lock(g_mutex); + auto* tc = LookupTitleCtxUnlocked(titleCtxId); + if (!tc) { + LOG_ERROR(Lib_NpScore, "invalid titleCtxId {}", titleCtxId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (static_cast(g_requests.size()) >= ORBIS_NP_SCORE_MAX_CTX_NUM) { + LOG_ERROR(Lib_NpScore, "too many requests ({})", g_requests.size()); + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + } + const OrbisNpScoreRequestId id = g_next_req_id++; + auto req = std::make_shared(); + req->titleCtxId = titleCtxId; + req->userId = tc->userId; + req->pcId = tc->pcId; + g_requests[id] = std::move(req); + LOG_INFO(Lib_NpScore, "CreateRequest id={} titleCtxId={}", id, titleCtxId); + return id; +} + +s32 PS4_SYSV_ABI sceNpScoreDeleteRequest(s32 reqId) { + LOG_INFO(Lib_NpScore, "DeleteRequest reqId={}", reqId); + std::lock_guard lock(g_mutex); + auto req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_ABORTED); + g_requests.erase(reqId); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpScoreAbortRequest(s32 reqId) { + LOG_INFO(Lib_NpScore, "AbortRequest reqId={}", reqId); + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + } + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + req->SetResult(ORBIS_NP_COMMUNITY_ERROR_ABORTED); + return ORBIS_OK; +} + +//*********************************** +// Async functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScorePollAsync(s32 reqId, s32* result) { + // return 0 = async op completed; *result holds its final return value + // return 1 = async op still in flight + // return <0 = this poll call itself failed (e.g. invalid reqId) + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + } + if (!req) { + LOG_ERROR(Lib_NpScore, "PollAsync invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + std::lock_guard rlock(req->mutex); + if (!req->result.has_value()) { + return 1; // still running + } + if (result != nullptr) { + *result = *req->result; + } + LOG_INFO(Lib_NpScore, "PollAsync reqId={} completed (result={:#x})", reqId, *req->result); + return 0; +} + +s32 PS4_SYSV_ABI sceNpScoreWaitAsync(s32 reqId, s32* result) { + // Block until the async op finishes. Shape mirrors PollAsync but waits on + // the request's cv instead of bailing out if result isn't set yet. + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + } + if (!req) { + LOG_ERROR(Lib_NpScore, "WaitAsync invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + std::unique_lock rlock(req->mutex); + req->cv.wait(rlock, [&] { return req->result.has_value(); }); + if (result != nullptr) { + *result = *req->result; + } + LOG_INFO(Lib_NpScore, "WaitAsync reqId={} completed (result={:#x})", reqId, *req->result); + return 0; +} + +//*********************************** +// Record Score functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreRecordScore(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, + const OrbisNpScoreComment* scoreComment, + const OrbisNpScoreGameInfo* gameInfo, + OrbisNpScoreRankNumber* tmpRank, + const Rtc::OrbisRtcTick* compareDate, void* option) { + LOG_INFO(Lib_NpScore, + "reqId={} boardId={} score={} scoreComment={} gameInfo={} tmpRank={} " + "compareDate={} option={}", + reqId, boardId, score, PTR(scoreComment), PTR(gameInfo), PTR(tmpRank), + PTR(compareDate), PTR(option)); + + if (option != nullptr) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (gameInfo != nullptr && gameInfo->infoSize > ORBIS_NP_SCORE_GAMEINFO_MAXSIZE) { + LOG_ERROR(Lib_NpScore, "gameInfo->infoSize {} exceeds max {}", gameInfo->infoSize, + ORBIS_NP_SCORE_GAMEINFO_MAXSIZE); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + // Stash the caller's tmpRank pointer so the reply handler can write to it. + req->tmpRankOut = tmpRank; + + const char* commentBytes = (scoreComment != nullptr) ? scoreComment->utf8Comment : nullptr; + const size_t commentLen = (scoreComment != nullptr) ? strnlen(scoreComment->utf8Comment, + ORBIS_NP_SCORE_COMMENT_MAXLEN) + : 0; + const u8* gameInfoBytes = (gameInfo != nullptr) ? gameInfo->data : nullptr; + const size_t gameInfoLen = (gameInfo != nullptr) ? gameInfo->infoSize : 0; + + const s32 dispatch_err = NpHandler::GetInstance().RecordScore( + req->userId, ServiceLabelForRequest(req), boardId, req->pcId, score, commentBytes, + commentLen, gameInfoBytes, gameInfoLen, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreRecordScoreAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, + const OrbisNpScoreComment* scoreComment, + const OrbisNpScoreGameInfo* gameInfo, + OrbisNpScoreRankNumber* tmpRank, + const Rtc::OrbisRtcTick* compareDate, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, score={}, " + "scoreComment={}, gameInfo={}, tmpRank={}, compareDate={}, option={}", + reqId, boardId, score, PTR(scoreComment), PTR(gameInfo), PTR(tmpRank), + PTR(compareDate), PTR(option)); + if (option != nullptr) { + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (gameInfo != nullptr && gameInfo->infoSize > ORBIS_NP_SCORE_GAMEINFO_MAXSIZE) { + LOG_ERROR(Lib_NpScore, "gameInfo->infoSize {} exceeds max {}", gameInfo->infoSize, + ORBIS_NP_SCORE_GAMEINFO_MAXSIZE); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + // Stash the caller's tmpRank pointer so the reply handler can write to it. + req->tmpRankOut = tmpRank; + + const char* commentBytes = (scoreComment != nullptr) ? scoreComment->utf8Comment : nullptr; + const size_t commentLen = (scoreComment != nullptr) ? strnlen(scoreComment->utf8Comment, + ORBIS_NP_SCORE_COMMENT_MAXLEN) + : 0; + const u8* gameInfoBytes = (gameInfo != nullptr) ? gameInfo->data : nullptr; + const size_t gameInfoLen = (gameInfo != nullptr) ? gameInfo->infoSize : 0; + + const s32 dispatch_err = NpHandler::GetInstance().RecordScore( + req->userId, ServiceLabelForRequest(req), boardId, req->pcId, score, commentBytes, + commentLen, gameInfoBytes, gameInfoLen, req); + if (dispatch_err != ORBIS_OK) { + // Dispatch failed synchronously (user signed out, not authed, etc.). + // Complete the request with the error so PollAsync/WaitAsync observe it. + req->SetResult(dispatch_err); + return dispatch_err; + } + return ORBIS_OK; +} + +//*********************************** +// RankingByRage functions +//*********************************** +static int GetRankingByRangeImpl(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankData* rankArray, + OrbisNpScoreRankDataA* aRankArrayIn, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "option argument is not supported"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum > ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "arrayNum {} exceeds max {}", arrayNum, + ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_REQUEST); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (startSerialRank == 0) { + LOG_ERROR(Lib_NpScore, "startSerialRank must be 1 or higher"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + const bool is_a_variant = (aRankArrayIn != nullptr); + if (is_a_variant == (rankArray != nullptr)) { + LOG_ERROR(Lib_NpScore, + "GetRankingByRangeImpl: exactly one of rankArray ({}) and aRankArray ({}) " + "must be non-null", + PTR(rankArray), PTR(aRankArrayIn)); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayNum == 0) { + LOG_ERROR(Lib_NpScore, "arrayNum must be > 0"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + const u64 expected_size = + arrayNum * (is_a_variant ? sizeof(OrbisNpScoreRankDataA) : sizeof(OrbisNpScoreRankData)); + if (rankArraySize != expected_size) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for rankArray (got {}, expected {} for {} variant)", + rankArraySize, expected_size, is_a_variant ? "A" : "non-A"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + + // Zero whichever output buffer the caller supplied. + if (is_a_variant) { + std::memset(aRankArrayIn, 0, rankArraySize); + } else { + std::memset(rankArray, 0, rankArraySize); + } + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment for commentArray"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment for infoArray"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + s32 dispatch_err; + if (is_a_variant) { + dispatch_err = NpHandler::GetInstance().GetRankingByRangeA( + req->userId, ServiceLabelForRequest(req), boardId, startSerialRank, + static_cast(arrayNum), aRankArrayIn, commentArray, infoArray, lastSortDate, + totalRecord, req); + } else { + dispatch_err = NpHandler::GetInstance().GetRankingByRange( + req->userId, ServiceLabelForRequest(req), boardId, startSerialRank, + static_cast(arrayNum), rankArray, commentArray, infoArray, lastSortDate, + totalRecord, req); + } + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRange( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, boardId={}, startSerialRank={}, arrayNum={}", reqId, + boardId, startSerialRank, arrayNum); + return GetRankingByRangeImpl(reqId, boardId, startSerialRank, rankArray, + /*aRankArrayIn=*/nullptr, rankArraySize, commentArray, + commentArraySize, infoArray, infoArraySize, arrayNum, lastSortDate, + totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, startSerialRank={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetRankingByRangeImpl(reqId, boardId, startSerialRank, rankArray, + /*aRankArrayIn=*/nullptr, rankArraySize, commentArray, + commentArraySize, infoArray, infoArraySize, arrayNum, lastSortDate, + totalRecord, option, true); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, startSerialRank={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetRankingByRangeImpl(reqId, boardId, startSerialRank, /*rankArray=*/nullptr, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, startSerialRank={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetRankingByRangeImpl(reqId, boardId, startSerialRank, /*rankArray=*/nullptr, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, true); +} + +//*********************************** +// Ranking by NpId functions +//*********************************** +static int GetRankingByNpIdImpl(s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npIdArray, + u64 npIdArraySize, OrbisNpScorePlayerRankData* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "Invalid argument"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum > ORBIS_NP_SCORE_MAX_NPID_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "Too many npIds requested: {}", arrayNum); + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + } + if (npIdArray == nullptr || rankArray == nullptr) { + LOG_ERROR(Lib_NpScore, "insufficient argument: npIdArray={}, rankArray={}", PTR(npIdArray), + PTR(rankArray)); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (npIdArraySize != arrayNum * sizeof(OrbisNpId) || + rankArraySize != arrayNum * sizeof(OrbisNpScorePlayerRankData)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: npIdArraySize={}, rankArraySize={}", + npIdArraySize, rankArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(rankArray, 0, rankArraySize); + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: commentArraySize={}", commentArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: infoArraySize={}", infoArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + std::vector npIds; + npIds.reserve(arrayNum); + for (u64 i = 0; i < arrayNum; ++i) { + const char* data = npIdArray[i].handle.data; + const size_t len = strnlen(data, sizeof(npIdArray[i].handle.data)); + npIds.emplace_back(data, len); + } + + const s32 dispatch_err = NpHandler::GetInstance().GetRankingByNpId( + req->userId, ServiceLabelForRequest(req), boardId, npIds, std::vector{}, rankArray, + commentArray, infoArray, lastSortDate, totalRecord, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npIdArray, u64 npIdArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, npIdArray={}, npIdArraySize={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, " + "option={}", + reqId, boardId, PTR(npIdArray), npIdArraySize, PTR(rankArray), rankArraySize, + PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, + PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return GetRankingByNpIdImpl(reqId, boardId, npIdArray, npIdArraySize, rankArray, rankArraySize, + commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, + lastSortDate, totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npIdArray, u64 npIdArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, boardId={}, arrayNum={}", reqId, boardId, arrayNum); + return GetRankingByNpIdImpl(reqId, boardId, npIdArray, npIdArraySize, rankArray, rankArraySize, + commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, + lastSortDate, totalRecord, option, true); +} + +//*********************************** +// Ranking by AccountId functions +//*********************************** +static int GetRankingByAccountIdImpl(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpAccountId* accountIdArray, u64 accountIdArraySize, + OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, + u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, + bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "Invalid argument"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum > ORBIS_NP_SCORE_MAX_ID_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "Too many accountIds requested: {}", arrayNum); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum == 0 || rankArray == nullptr || accountIdArray == nullptr) { + LOG_ERROR(Lib_NpScore, + "Insufficient arguments: arrayNum={}, rankArray={}, accountIdArray={}", arrayNum, + PTR(rankArray), PTR(accountIdArray)); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (accountIdArraySize != arrayNum * sizeof(OrbisNpAccountId)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for accountIdArray: size {} does not match expected {}", + accountIdArraySize, arrayNum * sizeof(OrbisNpAccountId)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (rankArraySize != arrayNum * sizeof(OrbisNpScorePlayerRankDataA)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for rankArray: size {} does not match expected {}", + rankArraySize, arrayNum * sizeof(OrbisNpScorePlayerRankDataA)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(rankArray, 0, rankArraySize); + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for commentArray: size {} does not match expected {}", + commentArraySize, arrayNum * sizeof(OrbisNpScoreComment)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for infoArray: size {} does not match expected {}", + infoArraySize, arrayNum * sizeof(OrbisNpScoreGameInfo)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + if (IsRequestAborted(req)) + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + + std::vector accountIds; + accountIds.reserve(arrayNum); + for (u64 i = 0; i < arrayNum; ++i) { + accountIds.push_back(static_cast(accountIdArray[i])); + } + + const s32 dispatch_err = NpHandler::GetInstance().GetRankingByAccountId( + req->userId, ServiceLabelForRequest(req), boardId, accountIds, + /*pcIds=*/{}, rankArray, commentArray, infoArray, lastSortDate, totalRecord, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) + return ORBIS_OK; + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, accountIdArray={}, " + "accountIdArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " + "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " + "totalRecord={}, option={}", + reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), rankArraySize, + PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, + PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return GetRankingByAccountIdImpl(reqId, boardId, accountIdArray, accountIdArraySize, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, + false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, " + "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " + "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " + "lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), rankArraySize, + PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, + PTR(lastSortDate), PTR(totalRecord), PTR(option)); + LOG_INFO(Lib_NpScore, + "reqId={} boardId={} accountIdArray={} accountIdArraySize={} arrayNum={} option={}", + reqId, boardId, PTR(accountIdArray), accountIdArraySize, arrayNum, PTR(option)); + return GetRankingByAccountIdImpl(reqId, boardId, accountIdArray, accountIdArraySize, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, + true); +} +//*********************************** +// Friends Ranking functions +//*********************************** +static int GetFriendsRankingImpl(s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, + OrbisNpScoreRankData* rankArray, + OrbisNpScoreRankDataA* aRankArrayIn, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, bool is_async) { + const bool is_a_variant = (aRankArrayIn != nullptr); + if (is_a_variant == (rankArray != nullptr)) { + LOG_ERROR(Lib_NpScore, + "GetFriendsRankingImpl: exactly one of rankArray ({}) and aRankArray ({}) " + "must be non-null", + PTR(rankArray), PTR(aRankArrayIn)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + const u64 expected_size = + arrayNum * (is_a_variant ? sizeof(OrbisNpScoreRankDataA) : sizeof(OrbisNpScoreRankData)); + if (rankArraySize != expected_size) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for rankArray (got {}, expected {} for {} variant)", + rankArraySize, expected_size, is_a_variant ? "A" : "non-A"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + auto* opt = static_cast(option); + u32 startSerialRank = 1; + if (includeSelf == 0) { + if (opt != nullptr) { + LOG_ERROR(Lib_NpScore, "includeSelf is 0 but option struct is not null"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + } else if (opt != nullptr) { + if (opt->size != sizeof(OrbisNpScoreGetFriendRankingOptParam)) { + LOG_ERROR(Lib_NpScore, "Invalid size for option struct: {}", opt->size); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (opt->startSerialRank != nullptr) { + startSerialRank = *opt->startSerialRank; + } + } + if (arrayNum > ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "arrayNum {} exceeds max {}", arrayNum, + ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_REQUEST); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (startSerialRank == 0) { + LOG_ERROR(Lib_NpScore, "startSerialRank must be 1 or higher"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum == 0) { + LOG_ERROR(Lib_NpScore, "arrayNum must be greater than 0"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + if (is_a_variant) { + std::memset(aRankArrayIn, 0, rankArraySize); + } else { + std::memset(rankArray, 0, rankArraySize); + } + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment for commentArray"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment for infoArray"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + s32 dispatch_err; + if (is_a_variant) { + dispatch_err = NpHandler::GetInstance().GetFriendsRankingA( + req->userId, ServiceLabelForRequest(req), boardId, includeSelf != 0, + static_cast(arrayNum), aRankArrayIn, commentArray, infoArray, lastSortDate, + totalRecord, req); + } else { + // todo support startSerialRank and opt->hits , currently we return the full friend set + // capped at max in score order without rank-slice semantics + dispatch_err = NpHandler::GetInstance().GetFriendsRanking( + req->userId, ServiceLabelForRequest(req), boardId, includeSelf != 0, + static_cast(arrayNum), rankArray, commentArray, infoArray, lastSortDate, + totalRecord, req); + } + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRanking(s32 reqId, OrbisNpScoreBoardId boardId, + s32 includeSelf, OrbisNpScoreRankData* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, includeSelf={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetFriendsRankingImpl(reqId, boardId, includeSelf, rankArray, /*aRankArrayIn=*/nullptr, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync( + s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankData* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, + OrbisNpScoreGetFriendRankingOptParam* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, includeSelf={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetFriendsRankingImpl(reqId, boardId, includeSelf, rankArray, /*aRankArrayIn=*/nullptr, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, true); +} +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRankingA(s32 reqId, OrbisNpScoreBoardId boardId, + s32 includeSelf, OrbisNpScoreRankDataA* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, + OrbisNpScoreGetFriendRankingOptParam* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, includeSelf={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetFriendsRankingImpl(reqId, boardId, includeSelf, /*rankArray=*/nullptr, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRankingAAsync( + s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankDataA* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, + OrbisNpScoreGetFriendRankingOptParam* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, includeSelf={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetFriendsRankingImpl(reqId, boardId, includeSelf, /*rankArray=*/nullptr, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, true); +} +//*********************************** +// Ranking ByNpIdPcId functions functions +//*********************************** +static int GetRankingByNpIdPcIdImpl(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpScoreNpIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, + u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, + bool is_async) { + + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "Invalid argument: option must be NULL"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + if (idArray == nullptr || arrayNum == 0 || rankArray == nullptr) { + LOG_ERROR(Lib_NpScore, "insufficient argument: idArray={}, arrayNum={}, rankArray={}", + PTR(idArray), arrayNum, PTR(rankArray)); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (arrayNum > ORBIS_NP_SCORE_MAX_NPID_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "Too many npIds requested: {}", arrayNum); + return ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + } + if (idArraySize != arrayNum * sizeof(OrbisNpScoreNpIdPcId) || + rankArraySize != arrayNum * sizeof(OrbisNpScorePlayerRankData)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: idArraySize={}, rankArraySize={}", idArraySize, + rankArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(rankArray, 0, rankArraySize); + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: commentArraySize={}", commentArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment: infoArraySize={}", infoArraySize); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + std::vector npIds; + std::vector pcIds; + npIds.reserve(arrayNum); + pcIds.reserve(arrayNum); + for (u64 i = 0; i < arrayNum; ++i) { + const char* data = idArray[i].npId.handle.data; + const size_t len = strnlen(data, sizeof(idArray[i].npId.handle.data)); + npIds.emplace_back(data, len); + pcIds.push_back(idArray[i].pcId); + } + + const s32 dispatch_err = NpHandler::GetInstance().GetRankingByNpId( + req->userId, ServiceLabelForRequest(req), boardId, npIds, pcIds, rankArray, commentArray, + infoArray, lastSortDate, totalRecord, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreNpIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, + "called reqId={}, boardId={}, idArray={}, idArraySize={}, " + "rankArray={}, rankArraySize={}, arrayNum={}", + reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, arrayNum); + return GetRankingByNpIdPcIdImpl(reqId, boardId, idArray, idArraySize, rankArray, rankArraySize, + commentArray, commentArraySize, infoArray, infoArraySize, + arrayNum, lastSortDate, totalRecord, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreNpIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, "called reqId={}, boardId={}, arrayNum={}", reqId, boardId, arrayNum); + return GetRankingByNpIdPcIdImpl(reqId, boardId, idArray, idArraySize, rankArray, rankArraySize, + commentArray, commentArraySize, infoArray, infoArraySize, + arrayNum, lastSortDate, totalRecord, option, true); +} + +//*********************************** +// Ranking ByAccountIdPcId functions +//*********************************** +static int GetRankingByAccountIdPcIdImpl(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpScoreAccountIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, + u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option, + bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "Invalid argument: option must be NULL"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum > ORBIS_NP_SCORE_MAX_ID_NUM_PER_REQUEST) { + LOG_ERROR(Lib_NpScore, "Too many accountIds requested: {}", arrayNum); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (arrayNum == 0 || rankArray == nullptr || idArray == nullptr) { + LOG_ERROR(Lib_NpScore, "Insufficient arguments: arrayNum={}, rankArray={}, idArray={}", + arrayNum, PTR(rankArray), PTR(idArray)); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (idArraySize != arrayNum * sizeof(OrbisNpScoreAccountIdPcId)) { + LOG_ERROR(Lib_NpScore, "Invalid alignment for idArray: size {} does not match expected {}", + idArraySize, arrayNum * sizeof(OrbisNpScoreAccountIdPcId)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + if (rankArraySize != arrayNum * sizeof(OrbisNpScorePlayerRankDataA)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for rankArray: size {} does not match expected {}", + rankArraySize, arrayNum * sizeof(OrbisNpScorePlayerRankDataA)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(rankArray, 0, rankArraySize); + if (commentArray != nullptr) { + if (commentArraySize != arrayNum * sizeof(OrbisNpScoreComment)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for commentArray: size {} does not match expected {}", + commentArraySize, arrayNum * sizeof(OrbisNpScoreComment)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(commentArray, 0, commentArraySize); + } + if (infoArray != nullptr) { + if (infoArraySize != arrayNum * sizeof(OrbisNpScoreGameInfo)) { + LOG_ERROR(Lib_NpScore, + "Invalid alignment for infoArray: size {} does not match expected {}", + infoArraySize, arrayNum * sizeof(OrbisNpScoreGameInfo)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + } + std::memset(infoArray, 0, infoArraySize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + if (IsRequestAborted(req)) + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + + std::vector accountIds; + std::vector pcIds; + accountIds.reserve(arrayNum); + pcIds.reserve(arrayNum); + for (u64 i = 0; i < arrayNum; ++i) { + accountIds.push_back(static_cast(idArray[i].accountId)); + pcIds.push_back(idArray[i].pcId); + } + + const s32 dispatch_err = NpHandler::GetInstance().GetRankingByAccountId( + req->userId, ServiceLabelForRequest(req), boardId, accountIds, pcIds, rankArray, + commentArray, infoArray, lastSortDate, totalRecord, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) + return ORBIS_OK; + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO( + Lib_NpScore, + "called reqId={}, boardId={}, idArray={}, " + "idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetRankingByAccountIdPcIdImpl(reqId, boardId, idArray, idArraySize, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, + false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_INFO( + Lib_NpScore, + "called reqId={}, boardId={}, idArray={}, " + "idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return GetRankingByAccountIdPcIdImpl(reqId, boardId, idArray, idArraySize, rankArray, + rankArraySize, commentArray, commentArraySize, infoArray, + infoArraySize, arrayNum, lastSortDate, totalRecord, option, + true); +} +//*********************************** +// BoardInfo functions +//*********************************** +static int GetBoardInfoImpl(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreBoardInfo* boardInfo, void* option, bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "GetBoardInfo: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (g_firmware_version >= 0 && g_firmware_version >= Common::ElfInfo::FW_25 && + boardInfo == nullptr) { + LOG_ERROR(Lib_NpScore, "GetBoardInfo: boardInfo must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + if (boardInfo == nullptr) { + LOG_INFO(Lib_NpScore, "GetBoardInfo: legacy SDK with null boardInfo — no-op success"); + return ORBIS_OK; + } + + std::memset(boardInfo, 0, sizeof(OrbisNpScoreBoardInfo)); + + const s32 dispatch_err = NpHandler::GetInstance().GetBoardInfo( + req->userId, ServiceLabelForRequest(req), boardId, boardInfo, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetBoardInfo(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreBoardInfo* boardInfo, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, boardId={}, boardInfo={}, option={}", reqId, boardId, + PTR(boardInfo), PTR(option)); + return GetBoardInfoImpl(reqId, boardId, boardInfo, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreBoardInfo* boardInfo, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, boardId={}, boardInfo={}, option={}", reqId, boardId, + PTR(boardInfo), PTR(option)); + return GetBoardInfoImpl(reqId, boardId, boardInfo, option, true); +} + +//*********************************** +// GetGameData functions +//*********************************** +static int GetGameDataImpl(s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npId, + u64* totalSize, u64 recvSize, void* data, void* option, bool is_async) { + if (npId == nullptr) { + LOG_ERROR(Lib_NpScore, "GetGameData: npId must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "GetGameData: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + const std::string npIdStr(npId->handle.data, + strnlen(npId->handle.data, sizeof(npId->handle.data))); + if (npIdStr.empty()) { + LOG_ERROR(Lib_NpScore, "GetGameData: npId.handle.data is empty"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + const s32 pcId = req->pcId; + + const s32 dispatch_err = + NpHandler::GetInstance().GetGameData(req->userId, ServiceLabelForRequest(req), boardId, + npIdStr, pcId, data, recvSize, totalSize, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +static int GetGameDataByAccountIdImpl(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpAccountId accountId, u64* totalSize, u64 recvSize, + void* data, void* option, bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "GetGameDataByAccountId: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (accountId == 0) { + LOG_ERROR(Lib_NpScore, "GetGameDataByAccountId: accountId must be non-zero"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + const s32 pcId = req->pcId; + + const s32 dispatch_err = NpHandler::GetInstance().GetGameDataByAccountId( + req->userId, ServiceLabelForRequest(req), boardId, accountId, pcId, data, recvSize, + totalSize, req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreGetGameData(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpId* npId, u64* totalSize, u64 recvSize, + void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, npId={}, totalSize={}, recvSize={}, data={}, option={}", + reqId, boardId, PTR(npId), PTR(totalSize), recvSize, PTR(data), PTR(option)); + return GetGameDataImpl(reqId, boardId, npId, totalSize, recvSize, data, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpId* npId, u64* totalSize, u64 recvSize, + void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, npId={}, totalSize={}, recvSize={}, data={}, option={}", + reqId, boardId, PTR(npId), PTR(totalSize), recvSize, PTR(data), PTR(option)); + return GetGameDataImpl(reqId, boardId, npId, totalSize, recvSize, data, option, true); +} + +s32 PS4_SYSV_ABI sceNpScoreGetGameDataByAccountId(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpAccountId accountId, u64* totalSize, + u64 recvSize, void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, accountId={}, totalSize={}, recvSize={}, data={}, " + "option={}", + reqId, boardId, accountId, PTR(totalSize), recvSize, PTR(data), PTR(option)); + return GetGameDataByAccountIdImpl(reqId, boardId, accountId, totalSize, recvSize, data, option, + false); +} + +s32 PS4_SYSV_ABI sceNpScoreGetGameDataByAccountIdAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpAccountId accountId, u64* totalSize, + u64 recvSize, void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, accountId={}, totalSize={}, recvSize={}, data={}, " + "option={}", + reqId, boardId, accountId, PTR(totalSize), recvSize, PTR(data), PTR(option)); + return GetGameDataByAccountIdImpl(reqId, boardId, accountId, totalSize, recvSize, data, option, + true); +} + +//*********************************** +// Record Game Data functions +//*********************************** +static int RecordGameDataImpl(s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreValue score, + u64 totalSize, u64 sendSize, const void* data, void* option, + bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "RecordGameData: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (data == nullptr) { + LOG_ERROR(Lib_NpScore, "RecordGameData: data must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + if (sendSize == 0) { + LOG_ERROR(Lib_NpScore, "RecordGameData: sendSize must be > 0"); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (totalSize > sendSize) { + // Chunked send not supported on the shadnet wire — the server + // stores whatever bytes arrive in this single command as the + // complete blob. Warn so callers can spot the truncation. + LOG_WARNING(Lib_NpScore, + "RecordGameData: chunked send not supported (totalSize={} sendSize={}); " + "storing first chunk only", + totalSize, sendSize); + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + const s32 pcId = req->pcId; + + const s32 dispatch_err = NpHandler::GetInstance().RecordGameData( + req->userId, ServiceLabelForRequest(req), boardId, pcId, score, + static_cast(data), static_cast(sendSize), req); + if (dispatch_err != ORBIS_OK) { + req->SetResult(dispatch_err); + return dispatch_err; + } + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreRecordGameData(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, u64 totalSize, u64 sendSize, + const void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, score={}, totalSize={}, sendSize={}, data={}, option={}", + reqId, boardId, score, totalSize, sendSize, PTR(data), PTR(option)); + return RecordGameDataImpl(reqId, boardId, score, totalSize, sendSize, data, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreRecordGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, u64 totalSize, u64 sendSize, + const void* data, void* option) { + LOG_INFO(Lib_NpScore, + "called reqId={}, boardId={}, score={}, totalSize={}, sendSize={}, data={}, option={}", + reqId, boardId, score, totalSize, sendSize, PTR(data), PTR(option)); + return RecordGameDataImpl(reqId, boardId, score, totalSize, sendSize, data, option, true); +} +//*********************************** +// Censor functions +//*********************************** +static int CensorCommentImpl(s32 reqId, const char* comment, void* option, bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "CensorComment: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (comment == nullptr) { + LOG_ERROR(Lib_NpScore, "CensorComment: comment must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + constexpr size_t probe = ORBIS_NP_SCORE_CENSOR_COMMENT_MAXLEN + 1; + const size_t len = strnlen(comment, probe); + if (len > ORBIS_NP_SCORE_CENSOR_COMMENT_MAXLEN) { + LOG_ERROR(Lib_NpScore, "CensorComment: comment too long (max {} chars + null)", + ORBIS_NP_SCORE_CENSOR_COMMENT_MAXLEN); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + // Passthrough + LOG_INFO(Lib_NpScore, "CensorComment: reqId={} len={} (passthrough)", reqId, len); + req->SetResult(ORBIS_OK); + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreCensorComment(s32 reqId, const char* comment, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, comment={}, option={}", reqId, + comment ? comment : "null", PTR(option)); + return CensorCommentImpl(reqId, comment, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreCensorCommentAsync(s32 reqId, const char* comment, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, comment={}, option={}", reqId, + comment ? comment : "null", PTR(option)); + return CensorCommentImpl(reqId, comment, option, true); +} + +//*********************************** +// Sanitize functions +//*********************************** +static int SanitizeCommentImpl(s32 reqId, const char* comment, char* sanitizedComment, void* option, + bool is_async) { + if (option != nullptr) { + LOG_ERROR(Lib_NpScore, "SanitizeComment: option must be null, got {}", PTR(option)); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + if (comment == nullptr) { + LOG_ERROR(Lib_NpScore, "SanitizeComment: comment must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + if (sanitizedComment == nullptr) { + LOG_ERROR(Lib_NpScore, "SanitizeComment: sanitizedComment must be non-null"); + return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + } + + std::shared_ptr req; + { + std::lock_guard lock(g_mutex); + req = LookupRequestUnlocked(reqId); + if (!req) { + LOG_ERROR(Lib_NpScore, "invalid reqId {}", reqId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; + } + if (IsRequestAborted(req)) { + return ORBIS_NP_COMMUNITY_ERROR_ABORTED; + } + } + + constexpr size_t probe = ORBIS_NP_SCORE_SANITIZE_COMMENT_MAXLEN + 1; + const size_t len = strnlen(comment, probe); + if (len > ORBIS_NP_SCORE_SANITIZE_COMMENT_MAXLEN) { + LOG_ERROR(Lib_NpScore, "SanitizeComment: comment too long (max {} chars + null)", + ORBIS_NP_SCORE_SANITIZE_COMMENT_MAXLEN); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + + // Passthrough copy + std::memcpy(sanitizedComment, comment, len); + sanitizedComment[len] = '\0'; + + LOG_INFO(Lib_NpScore, "SanitizeComment: reqId={} len={} (passthrough)", reqId, len); + req->SetResult(ORBIS_OK); + if (is_async) { + return ORBIS_OK; + } + return req->Wait(); +} + +s32 PS4_SYSV_ABI sceNpScoreSanitizeComment(s32 reqId, const char* comment, char* sanitizedComment, + void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, comment={}, sanitizedComment={}, option={}", reqId, + comment ? comment : "null", PTR(sanitizedComment), PTR(option)); + return SanitizeCommentImpl(reqId, comment, sanitizedComment, option, false); +} + +s32 PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync(s32 reqId, const char* comment, + char* sanitizedComment, void* option) { + LOG_INFO(Lib_NpScore, "called reqId={}, comment={}, sanitizedComment={}, option={}", reqId, + comment ? comment : "null", PTR(sanitizedComment), PTR(option)); + return SanitizeCommentImpl(reqId, comment, sanitizedComment, option, true); +} + +//*********************************** +// Misc functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(s32 ctxId, OrbisNpScorePcId pcId) { + if (pcId < 0) { + LOG_ERROR(Lib_NpScore, "SetPlayerCharacterId: pcId={} < 0", pcId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + std::lock_guard lock(g_mutex); + + // Try title-ctx pool first. Setting it here updates the default that + // subsequent CreateRequest will inherit. + if (auto* tc = LookupTitleCtxUnlocked(ctxId)) { + tc->pcId = pcId; + LOG_INFO(Lib_NpScore, "SetPlayerCharacterId: titleCtxId={} pcId={}", ctxId, pcId); + return ORBIS_OK; + } + + // Fall through to the request pool. Setting it here only affects + // operations dispatched on this specific request + if (auto req = LookupRequestUnlocked(ctxId)) { + req->pcId = pcId; + LOG_INFO(Lib_NpScore, "SetPlayerCharacterId: reqId={} pcId={}", ctxId, pcId); + return ORBIS_OK; + } + + LOG_ERROR(Lib_NpScore, "SetPlayerCharacterId: invalid id {}", ctxId); + return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID; +} + +//*********************************** +// Stubbed functions +//*********************************** + +int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners() { + LOG_ERROR(Lib_NpScore, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreCreateTitleCtx() { + LOG_ERROR(Lib_NpScore, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSave( + s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, + OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, includeSelf={}, " + "rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, infoArray={}, " + "infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSaveAsync( + s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, + OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option) { + LOG_ERROR( + Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "includeSelf={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, includeSelf, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSave( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " + "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " + "lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), + rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, + arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "accountIdArray={}, accountIdArraySize={}, rankArray={}, rankArraySize={}, " + "commentArray={}, commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, " + "lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, PTR(accountIdArray), accountIdArraySize, PTR(rankArray), + rankArraySize, PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, + arrayNum, PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "idArray={}, idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " + "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " + "totalRecord={}, option={}", + reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, + PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, + PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "idArray={}, idArraySize={}, rankArray={}, rankArraySize={}, commentArray={}, " + "commentArraySize={}, infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, " + "totalRecord={}, option={}", + reqId, boardId, PTR(idArray), idArraySize, PTR(rankArray), rankArraySize, + PTR(commentArray), commentArraySize, PTR(infoArray), infoArraySize, arrayNum, + PTR(lastSortDate), PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR( + Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "startSerialRank={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSaveAsync( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option) { + LOG_ERROR( + Lib_NpScore, + "(STUBBED) called reqId={}, boardId={}, " + "startSerialRank={}, rankArray={}, rankArraySize={}, commentArray={}, commentArraySize={}, " + "infoArray={}, infoArraySize={}, arrayNum={}, lastSortDate={}, totalRecord={}, option={}", + reqId, boardId, startSerialRank, PTR(rankArray), rankArraySize, PTR(commentArray), + commentArraySize, PTR(infoArray), infoArraySize, arrayNum, PTR(lastSortDate), + PTR(totalRecord), PTR(option)); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreSetThreadParam(s32 threadPriority, u64 cpuAffinityMask) { + LOG_ERROR(Lib_NpScore, "(STUBBED) called threadPriority={}, cpuAffinityMask={:#x}", + threadPriority, cpuAffinityMask); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpScoreSetTimeout(s32 id, s32 resolveRetry, s32 resolveTimeout, s32 connTimeout, + s32 sendTimeout, s32 recvTimeout) { + LOG_ERROR(Lib_NpScore, + "(STUBBED) called id={}, resolveRetry={}, resolveTimeout={}, " + "connTimeout={}, sendTimeout={}, recvTimeout={}", + id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + ASSERT_MSG(Libraries::Kernel::sceKernelGetCompiledSdkVersion(&g_firmware_version) == ORBIS_OK, + "Failed to get compiled SDK version."); + + LIB_FUNCTION("1i7kmKbX6hk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreAbortRequest); + LIB_FUNCTION("2b3TI0mDYiI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCensorComment); + LIB_FUNCTION("4eOvDyN-aZc", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCensorCommentAsync); + LIB_FUNCTION("dTXC+YcePtM", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreChangeModeForOtherSaveDataOwners); + LIB_FUNCTION("KnNA1TEgtBI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateNpTitleCtx); + LIB_FUNCTION("GWnWQNXZH5M", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateNpTitleCtxA); + LIB_FUNCTION("gW8qyjYrUbk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateRequest); + LIB_FUNCTION("qW9M0bQ-Zx0", "libSceNpScore", 1, "libSceNpScore", sceNpScoreCreateTitleCtx); + LIB_FUNCTION("G0pE+RNCwfk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreDeleteNpTitleCtx); + LIB_FUNCTION("dK8-SgYf6r4", "libSceNpScore", 1, "libSceNpScore", sceNpScoreDeleteRequest); + LIB_FUNCTION("LoVMVrijVOk", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetBoardInfo); + LIB_FUNCTION("Q0Avi9kebsY", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetBoardInfoAsync); + LIB_FUNCTION("8kuIzUw6utQ", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetFriendsRanking); + LIB_FUNCTION("gMbOn+-6eXA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetFriendsRankingA); + LIB_FUNCTION("6-G9OxL5DKg", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetFriendsRankingAAsync); + LIB_FUNCTION("7SuMUlN7Q6I", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetFriendsRankingAsync); + LIB_FUNCTION("AgcxgceaH8k", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetFriendsRankingForCrossSave); + LIB_FUNCTION("m6F7sE1HQZU", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetFriendsRankingForCrossSaveAsync); + LIB_FUNCTION("zKoVok6FFEI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetGameData); + LIB_FUNCTION("JjOFRVPdQWc", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetGameDataAsync); + LIB_FUNCTION("Lmtc9GljeUA", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetGameDataByAccountId); + LIB_FUNCTION("PP9jx8s0574", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetGameDataByAccountIdAsync); + LIB_FUNCTION("K9tlODTQx3c", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountId); + LIB_FUNCTION("dRszNNyGWkw", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdAsync); + LIB_FUNCTION("3Ybj4E1qNtY", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdForCrossSave); + LIB_FUNCTION("Kc+3QK84AKM", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdForCrossSaveAsync); + LIB_FUNCTION("wJPWycVGzrs", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdPcId); + LIB_FUNCTION("bFVjDgxFapc", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdPcIdAsync); + LIB_FUNCTION("oXjVieH6ZGQ", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdPcIdForCrossSave); + LIB_FUNCTION("nXaF1Bxb-Nw", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync); + LIB_FUNCTION("9mZEgoiEq6Y", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByNpId); + LIB_FUNCTION("Rd27dqUFZV8", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdAsync); + LIB_FUNCTION("ETS-uM-vH9Q", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdPcId); + LIB_FUNCTION("FsouSN0ykN8", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdPcIdAsync); + LIB_FUNCTION("KBHxDjyk-jA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByRange); + LIB_FUNCTION("MA9vSt7JImY", "libSceNpScore", 1, "libSceNpScore", sceNpScoreGetRankingByRangeA); + LIB_FUNCTION("y5ja7WI05rs", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByRangeAAsync); + LIB_FUNCTION("rShmqXHwoQE", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByRangeAsync); + LIB_FUNCTION("nRoYV2yeUuw", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByRangeForCrossSave); + LIB_FUNCTION("AZ4eAlGDy-Q", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreGetRankingByRangeForCrossSaveAsync); + LIB_FUNCTION("m1DfNRstkSQ", "libSceNpScore", 1, "libSceNpScore", sceNpScorePollAsync); + LIB_FUNCTION("bcoVwcBjQ9E", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordGameData); + LIB_FUNCTION("1gL5PwYzrrw", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordGameDataAsync); + LIB_FUNCTION("zT0XBtgtOSI", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordScore); + LIB_FUNCTION("ANJssPz3mY0", "libSceNpScore", 1, "libSceNpScore", sceNpScoreRecordScoreAsync); + LIB_FUNCTION("r4oAo9in0TA", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSanitizeComment); + LIB_FUNCTION("3UVqGJeDf30", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreSanitizeCommentAsync); + LIB_FUNCTION("bygbKdHmjn4", "libSceNpScore", 1, "libSceNpScore", + sceNpScoreSetPlayerCharacterId); + LIB_FUNCTION("yxK68584JAU", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSetThreadParam); + LIB_FUNCTION("S3xZj35v8Z8", "libSceNpScore", 1, "libSceNpScore", sceNpScoreSetTimeout); + LIB_FUNCTION("fqk8SC63p1U", "libSceNpScore", 1, "libSceNpScore", sceNpScoreWaitAsync); + LIB_FUNCTION("KnNA1TEgtBI", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreCreateNpTitleCtx); + LIB_FUNCTION("8kuIzUw6utQ", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetFriendsRanking); + LIB_FUNCTION("7SuMUlN7Q6I", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetFriendsRankingAsync); + LIB_FUNCTION("zKoVok6FFEI", "libSceNpScoreCompat", 1, "libSceNpScore", sceNpScoreGetGameData); + LIB_FUNCTION("JjOFRVPdQWc", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetGameDataAsync); + LIB_FUNCTION("9mZEgoiEq6Y", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByNpId); + LIB_FUNCTION("Rd27dqUFZV8", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdAsync); + LIB_FUNCTION("ETS-uM-vH9Q", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdPcId); + LIB_FUNCTION("FsouSN0ykN8", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByNpIdPcIdAsync); + LIB_FUNCTION("KBHxDjyk-jA", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByRange); + LIB_FUNCTION("rShmqXHwoQE", "libSceNpScoreCompat", 1, "libSceNpScore", + sceNpScoreGetRankingByRangeAsync); +}; + +} // namespace Libraries::Np::NpScore \ No newline at end of file diff --git a/src/core/libraries/np/np_score.h b/src/core/libraries/np/np_score/np_score.h similarity index 64% rename from src/core/libraries/np/np_score.h rename to src/core/libraries/np/np_score/np_score.h index 0705707b6..3ac5dc689 100644 --- a/src/core/libraries/np/np_score.h +++ b/src/core/libraries/np/np_score/np_score.h @@ -14,6 +14,8 @@ class SymbolsResolver; namespace Libraries::Np::NpScore { +using OrbisNpScoreTitleCtxId = s32; +using OrbisNpScoreRequestId = s32; using OrbisNpScoreBoardId = u32; using OrbisNpScorePcId = s32; using OrbisNpScoreRankNumber = u32; @@ -21,6 +23,30 @@ using OrbisNpScoreValue = s64; constexpr int ORBIS_NP_SCORE_COMMENT_MAXLEN = 63; constexpr int ORBIS_NP_SCORE_GAMEINFO_MAXSIZE = 189; +constexpr int ORBIS_NP_SCORE_CENSOR_COMMENT_MAXLEN = 255; +constexpr int ORBIS_NP_SCORE_SANITIZE_COMMENT_MAXLEN = 255; + +constexpr int ORBIS_NP_SCORE_NORMAL_UPDATE = 0; +constexpr int ORBIS_NP_SCORE_FORCE_UPDATE = 1; +constexpr int ORBIS_NP_SCORE_DESCENDING_ORDER = 0; +constexpr int ORBIS_NP_SCORE_ASCENDING_ORDER = 1; + +constexpr int ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_REQUEST = 100; +constexpr int ORBIS_NP_SCORE_MAX_ID_NUM_PER_REQUEST = 101; +constexpr int ORBIS_NP_SCORE_MAX_SELECTED_FRIENDS_NUM = 100; + +constexpr int ORBIS_NP_SCORE_MAX_RANGE_NUM_PER_TRANS = 100; +constexpr int ORBIS_NP_SCORE_MAX_NPID_NUM_PER_TRANS = 101; +constexpr int ORBIS_NP_SCORE_MAX_NPID_NUM_PER_REQUEST = 101; + +constexpr int ORBIS_NP_SCORE_MAX_CTX_NUM = 32; + +constexpr int ORBIS_NP_SCORE_BINDMODE_ALL_FORBIDDEN = 0x0000; +constexpr int ORBIS_NP_SCORE_BINDMODE_RDONLY = 0x0001; +constexpr int ORBIS_NP_SCORE_BINDMODE_WRONLY = 0x0002; +constexpr int ORBIS_NP_SCORE_BINDMODE_RDWR = + ORBIS_NP_SCORE_BINDMODE_RDONLY | ORBIS_NP_SCORE_BINDMODE_WRONLY; +constexpr int ORBIS_NP_SCORE_BINDMODE_DEFAULT = ORBIS_NP_SCORE_BINDMODE_RDWR; struct OrbisNpScoreBoardInfo { u32 rankLimit; @@ -29,16 +55,19 @@ struct OrbisNpScoreBoardInfo { OrbisNpScoreRankNumber uploadNumLimit; u64 uploadSizeLimit; }; +static_assert(sizeof(OrbisNpScoreBoardInfo) == 24, "OrbisNpScoreBoardInfo must be 24 bytes"); struct OrbisNpScoreComment { char utf8Comment[ORBIS_NP_SCORE_COMMENT_MAXLEN + 1]; }; +static_assert(sizeof(OrbisNpScoreComment) == 64, "OrbisNpScoreComment must be 64 bytes"); struct OrbisNpScoreGameInfo { u64 infoSize; u8 data[ORBIS_NP_SCORE_GAMEINFO_MAXSIZE]; u8 pad2[3]; }; +static_assert(sizeof(OrbisNpScoreGameInfo) == 200, "OrbisNpScoreGameInfo must be 200 bytes"); struct OrbisNpScoreGetFriendRankingOptParam { u64 size; @@ -71,6 +100,7 @@ struct OrbisNpScoreRankData { OrbisNpScoreValue scoreValue; Rtc::OrbisRtcTick recordDate; }; +static_assert(sizeof(OrbisNpScoreRankData) == 0x80, "OrbisNpScoreRankData must be 128 bytes"); struct OrbisNpScoreRankDataA { OrbisNpOnlineId onlineId; @@ -88,12 +118,23 @@ struct OrbisNpScoreRankDataA { OrbisNpAccountId accountId; u8 pad2[8]; }; +static_assert(sizeof(OrbisNpScoreRankDataA) == 0x90, "OrbisNpScoreRankDataA must be 144 bytes"); + +struct OrbisNpScorePlayerRankData { + s32 hasData; + u8 pad0[4]; + OrbisNpScoreRankData rankData; +}; +static_assert(sizeof(OrbisNpScorePlayerRankData) == 0x88, + "OrbisNpScorePlayerRankData must be 136 bytes"); struct OrbisNpScorePlayerRankDataA { s32 hasData; u8 pad0[4]; OrbisNpScoreRankDataA rankData; }; +static_assert(sizeof(OrbisNpScorePlayerRankDataA) == 0x98, + "OrbisNpScorePlayerRankDataA must be 152 bytes"); struct OrbisNpScoreRankDataForCrossSave { OrbisNpId npId; @@ -116,29 +157,93 @@ struct OrbisNpScorePlayerRankDataForCrossSave { u8 pad0[4]; OrbisNpScoreRankDataForCrossSave rankData; }; -int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId); -// stubbed -int PS4_SYSV_ABI sceNpScoreAbortRequest(s32 reqId); -int PS4_SYSV_ABI sceNpScoreCensorComment(s32 reqId, const char* comment, void* option); -int PS4_SYSV_ABI sceNpScoreCensorCommentAsync(s32 reqId, const char* comment, void* option); -int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners(); + +//*********************************** +// Title context management functions +//*********************************** +int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, + const OrbisNpId* selfNpId); int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA(OrbisNpServiceLabel npServiceLabel, UserService::OrbisUserServiceUserId selfId); -int PS4_SYSV_ABI sceNpScoreCreateRequest(s32 titleCtxId); -int PS4_SYSV_ABI sceNpScoreCreateTitleCtx(); -int PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(s32 titleCtxId); -int PS4_SYSV_ABI sceNpScoreDeleteRequest(s32 reqId); -int PS4_SYSV_ABI sceNpScoreGetBoardInfo(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreBoardInfo* boardInfo, void* option); -int PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreBoardInfo* boardInfo, void* option); -int PS4_SYSV_ABI sceNpScoreGetFriendsRanking(s32 reqId, OrbisNpScoreBoardId boardId, +s32 PS4_SYSV_ABI sceNpScoreDeleteNpTitleCtx(s32 titleCtxId); +//*********************************** +// Request management functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreCreateRequest(s32 titleCtxId); +s32 PS4_SYSV_ABI sceNpScoreDeleteRequest(s32 reqId); +s32 PS4_SYSV_ABI sceNpScoreAbortRequest(s32 reqId); +//*********************************** +// Async functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScorePollAsync(s32 reqId, s32* result); +s32 PS4_SYSV_ABI sceNpScoreWaitAsync(s32 reqId, s32* result); +//*********************************** +// Record Score functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreRecordScore(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, + const OrbisNpScoreComment* scoreComment, + const OrbisNpScoreGameInfo* gameInfo, + OrbisNpScoreRankNumber* tmpRank, + const Rtc::OrbisRtcTick* compareDate, void* option); +s32 PS4_SYSV_ABI sceNpScoreRecordScoreAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, + const OrbisNpScoreComment* scoreComment, + const OrbisNpScoreGameInfo* gameInfo, + OrbisNpScoreRankNumber* tmpRank, + const Rtc::OrbisRtcTick* compareDate, void* option); +//*********************************** +// RankingByRage functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRange( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( + s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, + OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +//*********************************** +// Ranking by NpId functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npIdArray, u64 npIdArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpId* npIdArray, u64 npIdArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +//*********************************** +// Friend Ranking functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRanking(s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync( + s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankData* rankArray, + u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, + OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, + OrbisNpScoreGetFriendRankingOptParam* option); int PS4_SYSV_ABI sceNpScoreGetFriendsRankingA(s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, @@ -153,12 +258,102 @@ int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAAsync( OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option); -int PS4_SYSV_ABI sceNpScoreGetFriendsRankingAsync( - s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankData* rankArray, - u64 rankArraySize, OrbisNpScoreComment* commentArray, u64 commentArraySize, - OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, - OrbisNpScoreGetFriendRankingOptParam* option); +//*********************************** +// Ranking ByNpIdPcId functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreNpIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByNpIdPcIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreNpIdPcId* idArray, u64 idArraySize, + OrbisNpScorePlayerRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, + u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, + Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); +//*********************************** +// Ranking by AccountId functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, + u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option); +//*********************************** +// Ranking ByAccountIdPcId functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcId( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdAsync( + s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, + u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, + OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, + u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, + OrbisNpScoreRankNumber* totalRecord, void* option); +//*********************************** +// BoardInfo functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetBoardInfo(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreBoardInfo* boardInfo, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetBoardInfoAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreBoardInfo* boardInfo, void* option); +//*********************************** +// GetGameData functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreGetGameData(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpId* npId, u64* totalSize, u64 recvSize, + void* data, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, + const OrbisNpId* npId, u64* totalSize, u64 recvSize, + void* data, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetGameDataByAccountId(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpAccountId accountId, u64* totalSize, + u64 recvSize, void* data, void* option); +s32 PS4_SYSV_ABI sceNpScoreGetGameDataByAccountIdAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpAccountId accountId, u64* totalSize, + u64 recvSize, void* data, void* option); +//*********************************** +// Record Game Data functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreRecordGameData(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, u64 totalSize, u64 sendSize, + const void* data, void* option); +s32 PS4_SYSV_ABI sceNpScoreRecordGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, + OrbisNpScoreValue score, u64 totalSize, u64 sendSize, + const void* data, void* option); +//*********************************** +// Censor functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreCensorComment(s32 reqId, const char* comment, void* option); +s32 PS4_SYSV_ABI sceNpScoreCensorCommentAsync(s32 reqId, const char* comment, void* option); +//*********************************** +// Sanitize functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreSanitizeComment(s32 reqId, const char* comment, char* sanitizedComment, + void* option); +s32 PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync(s32 reqId, const char* comment, + char* sanitizedComment, void* option); +//*********************************** +// Misc functions +//*********************************** +s32 PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(s32 ctxId, OrbisNpScorePcId pcId); +//*********************************** +// Stubbed functions +//*********************************** + +int PS4_SYSV_ABI sceNpScoreChangeModeForOtherSaveDataOwners(); +int PS4_SYSV_ABI sceNpScoreCreateTitleCtx(); int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSave( s32 reqId, OrbisNpScoreBoardId boardId, s32 includeSelf, OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, @@ -171,26 +366,6 @@ int PS4_SYSV_ABI sceNpScoreGetFriendsRankingForCrossSaveAsync( OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option); -int PS4_SYSV_ABI sceNpScoreGetGameData(); -int PS4_SYSV_ABI sceNpScoreGetGameDataAsync(); -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountId(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpAccountId accountId, u64* totalSize, - u64 recvSize, void* data, void* option); -int PS4_SYSV_ABI sceNpScoreGetGameDataByAccountIdAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpAccountId accountId, u64* totalSize, - u64 recvSize, void* data, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountId( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, - u64 accountIdArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option); int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSave( s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpAccountId* accountIdArray, u64 accountIdArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, @@ -203,18 +378,6 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync( OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcId( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdAsync( - s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, - u64 idArraySize, OrbisNpScorePlayerRankDataA* rankArray, u64 rankArraySize, - OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, - u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, - OrbisNpScoreRankNumber* totalRecord, void* option); int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave( s32 reqId, OrbisNpScoreBoardId boardId, const OrbisNpScoreAccountIdPcId* idArray, u64 idArraySize, OrbisNpScorePlayerRankDataForCrossSave* rankArray, u64 rankArraySize, @@ -227,30 +390,6 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync( OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -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( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeA( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAAsync( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankDataA* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScoreGetRankingByRangeAsync( - s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, - OrbisNpScoreRankData* rankArray, u64 rankArraySize, OrbisNpScoreComment* commentArray, - u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, - Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSave( s32 reqId, OrbisNpScoreBoardId boardId, OrbisNpScoreRankNumber startSerialRank, OrbisNpScoreRankDataForCrossSave* rankArray, u64 rankArraySize, @@ -263,34 +402,9 @@ int PS4_SYSV_ABI sceNpScoreGetRankingByRangeForCrossSaveAsync( OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray, u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate, OrbisNpScoreRankNumber* totalRecord, void* option); -int PS4_SYSV_ABI sceNpScorePollAsync(s32 reqId, s32* result); -int PS4_SYSV_ABI sceNpScoreRecordGameData(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, u64 totalSize, u64 sendSize, - const void* data, void* option); -int PS4_SYSV_ABI sceNpScoreRecordGameDataAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, u64 totalSize, u64 sendSize, - const void* data, void* option); -int PS4_SYSV_ABI sceNpScoreRecordScore(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, - const OrbisNpScoreComment* scoreComment, - const OrbisNpScoreGameInfo* gameInfo, - OrbisNpScoreRankNumber* tmpRank, - const Rtc::OrbisRtcTick* compareDate, void* option); -int PS4_SYSV_ABI sceNpScoreRecordScoreAsync(s32 reqId, OrbisNpScoreBoardId boardId, - OrbisNpScoreValue score, - const OrbisNpScoreComment* scoreComment, - const OrbisNpScoreGameInfo* gameInfo, - OrbisNpScoreRankNumber* tmpRank, - const Rtc::OrbisRtcTick* compareDate, void* option); -int PS4_SYSV_ABI sceNpScoreSanitizeComment(s32 reqId, const char* comment, char* sanitizedComment, - void* option); -int PS4_SYSV_ABI sceNpScoreSanitizeCommentAsync(s32 reqId, const char* comment, - char* sanitizedComment, void* option); -int PS4_SYSV_ABI sceNpScoreSetPlayerCharacterId(s32 ctxId, OrbisNpScorePcId pcId); int PS4_SYSV_ABI sceNpScoreSetThreadParam(s32 threadPriority, u64 cpuAffinityMask); int PS4_SYSV_ABI sceNpScoreSetTimeout(s32 id, s32 resolveRetry, s32 resolveTimeout, s32 connTimeout, s32 sendTimeout, s32 recvTimeout); -int PS4_SYSV_ABI sceNpScoreWaitAsync(s32 reqId, s32* result); void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpScore diff --git a/src/core/libraries/np/np_score/np_score_ctx.h b/src/core/libraries/np/np_score/np_score_ctx.h new file mode 100644 index 000000000..1b2d2271a --- /dev/null +++ b/src/core/libraries/np/np_score/np_score_ctx.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include "common/types.h" + +namespace Libraries::Np::NpScore { + +// Per-request state shared between np_score.cpp (creator/waiter) and the +// handler that fulfils the request (np_handler.cpp) +struct ScoreRequestCtx { + std::mutex mutex; + std::condition_variable cv; + std::optional result; // nullopt = still pending + + s32 titleCtxId = 0; + s32 userId = -1; + s32 pcId = 0; + u32* tmpRankOut = nullptr; + + void SetResult(s32 r) { + { + std::lock_guard lock(mutex); + if (result.has_value()) { + return; + } + result = r; + } + cv.notify_all(); + } + + // Block until result is set. Used by the synced functions + s32 Wait() { + std::unique_lock lock(mutex); + cv.wait(lock, [this] { return result.has_value(); }); + return *result; + } +}; + +} // namespace Libraries::Np::NpScore diff --git a/src/core/libraries/np/np_trophy.cpp b/src/core/libraries/np/np_trophy.cpp index 7dafc3284..21a4a16f5 100644 --- a/src/core/libraries/np/np_trophy.cpp +++ b/src/core/libraries/np/np_trophy.cpp @@ -12,7 +12,6 @@ #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_trophy.h" -#include "core/libraries/np/np_trophy_error.h" #include "core/libraries/np/trophy_ui.h" #include "core/libraries/system/userservice.h" #include "core/memory.h" diff --git a/src/core/libraries/np/np_trophy_error.h b/src/core/libraries/np/np_trophy_error.h deleted file mode 100644 index 173d7dd59..000000000 --- a/src/core/libraries/np/np_trophy_error.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/error_codes.h" - -// NpTrophy library -constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600; -constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601; -constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602; -constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604; -constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605; -constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606; -constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B; -constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C; -constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D; -constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E; -constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F; -constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610; -constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611; -constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612; -constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; -constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616; -constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618; -constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619; -constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A; -constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C; -constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D; -constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E; -constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621; -constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622; -constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623; -constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; -constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625; -constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626; -constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627; -constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628; -constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629; -constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B; -constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D; -constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND = 0x805516C2; diff --git a/src/core/libraries/np/np_web_api.cpp b/src/core/libraries/np/np_web_api.cpp index 0c633e0d1..3df411208 100644 --- a/src/core/libraries/np/np_web_api.cpp +++ b/src/core/libraries/np/np_web_api.cpp @@ -5,8 +5,8 @@ #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_web_api.h" -#include "core/libraries/np/np_web_api_error.h" #include "core/libraries/np/np_web_api_internal.h" #include diff --git a/src/core/libraries/np/np_web_api2.cpp b/src/core/libraries/np/np_web_api2.cpp index 855251097..e7489ccba 100644 --- a/src/core/libraries/np/np_web_api2.cpp +++ b/src/core/libraries/np/np_web_api2.cpp @@ -5,8 +5,8 @@ #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/np/np_error.h" #include "core/libraries/np/np_web_api2.h" -#include "core/libraries/np/np_web_api2_error.h" #include "core/libraries/system/userservice.h" namespace Libraries::Np::NpWebApi2 { diff --git a/src/core/libraries/np/np_web_api2_error.h b/src/core/libraries/np/np_web_api2_error.h deleted file mode 100644 index 6ab4fdb31..000000000 --- a/src/core/libraries/np/np_web_api2_error.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/error_codes.h" - -constexpr int ORBIS_NP_WEBAPI2_ERROR_OUT_OF_MEMORY = 0x80553401; -constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = 0x80553402; -constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID = 0x80553403; -constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80553404; -constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND = 0x80553405; -constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406; -constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407; -constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408; -constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409; -constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a; -constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b; -constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c; -constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d; -constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e; -constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f; -constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410; -constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411; -constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412; -constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413; -constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414; -constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415; -constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416; -constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417; -constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418; -constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419; -constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a; -constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b; -constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c; \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api_error.h b/src/core/libraries/np/np_web_api_error.h deleted file mode 100644 index c7f08224f..000000000 --- a/src/core/libraries/np/np_web_api_error.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_NP_WEBAPI_ERROR_OUT_OF_MEMORY = 0x80552901; -constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT = 0x80552902; -constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = 0x80552903; -constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80552904; -constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = 0x80552905; -constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = 0x80552906; -constexpr int ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN = 0x80552907; -constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = 0x80552908; -constexpr int ORBIS_NP_WEBAPI_ERROR_ABORTED = 0x80552909; -constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055290a; -constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290b; -constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290c; -constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = 0x8055290d; -constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290e; -constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290f; -constexpr int ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x80552910; -constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY = 0x80552911; -constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY = 0x80552912; -constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY = 0x80552913; -constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_HTTP_STATUS_CODE = 0x80552914; -constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_HTTP_HEADER = 0x80552915; -constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_FUNCTION_CALL = 0x80552916; -constexpr int ORBIS_NP_WEBAPI_ERROR_MULTIPART_PART_NOT_FOUND = 0x80552917; -constexpr int ORBIS_NP_WEBAPI_ERROR_PARAMETER_TOO_LONG = 0x80552918; -constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY = 0x80552919; -constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = 0x8055291a; -constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX = 0x8055291b; -constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055291c; -constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055291d; -constexpr int ORBIS_NP_WEBAPI_ERROR_AFTER_SEND = 0x8055291e; -constexpr int ORBIS_NP_WEBAPI_ERROR_TIMEOUT = 0x8055291f; diff --git a/src/core/libraries/np/np_web_api_internal.h b/src/core/libraries/np/np_web_api_internal.h index 571df0ab9..5a221a608 100644 --- a/src/core/libraries/np/np_web_api_internal.h +++ b/src/core/libraries/np/np_web_api_internal.h @@ -7,8 +7,8 @@ #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_web_api.h" -#include "core/libraries/np/np_web_api_error.h" namespace Libraries::Np::NpWebApi { diff --git a/src/core/libraries/share_play/shareplay.cpp b/src/core/libraries/share_play/shareplay.cpp index f6865ba70..bf6d72076 100644 --- a/src/core/libraries/share_play/shareplay.cpp +++ b/src/core/libraries/share_play/shareplay.cpp @@ -15,14 +15,24 @@ int PS4_SYSV_ABI sceSharePlayCrashDaemon() { } int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfo(OrbisSharePlayConnectionInfo* pInfo) { + if (pInfo == nullptr) { + LOG_ERROR(Lib_SharePlay, "sceSharePlayGetCurrentConnectionInfo: pInfo is null"); + return ORBIS_OK; + } memset(pInfo, 0, sizeof(*pInfo)); pInfo->status = ORBIS_SHARE_PLAY_CONNECTION_STATUS_DORMANT; - LOG_DEBUG(Lib_SharePlay, "(STUBBED) called"); + LOG_DEBUG(Lib_SharePlay, "sceSharePlayGetCurrentConnectionInfo: returning DORMANT"); return ORBIS_OK; } -int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA() { - LOG_ERROR(Lib_SharePlay, "(STUBBED) called"); +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA(OrbisSharePlayConnectionInfoA* pInfo) { + if (pInfo == nullptr) { + LOG_ERROR(Lib_SharePlay, "sceSharePlayGetCurrentConnectionInfoA: pInfo is null"); + return ORBIS_OK; + } + memset(pInfo, 0, sizeof(*pInfo)); + pInfo->status = ORBIS_SHARE_PLAY_CONNECTION_STATUS_DORMANT; + LOG_DEBUG(Lib_SharePlay, "sceSharePlayGetCurrentConnectionInfoA: returning DORMANT"); return ORBIS_OK; } diff --git a/src/core/libraries/share_play/shareplay.h b/src/core/libraries/share_play/shareplay.h index b67b01e93..4eaa12fdb 100644 --- a/src/core/libraries/share_play/shareplay.h +++ b/src/core/libraries/share_play/shareplay.h @@ -26,9 +26,20 @@ struct OrbisSharePlayConnectionInfo { Libraries::UserService::OrbisUserServiceUserId visitorUserId; }; +struct OrbisSharePlayConnectionInfoA { + int status; + int mode; + Libraries::Np::OrbisNpOnlineId hostOnlineId; + Libraries::Np::OrbisNpOnlineId visitorOnlineId; + Libraries::Np::OrbisNpAccountId hostAccountId; + Libraries::Np::OrbisNpAccountId visitorAccountId; + Libraries::UserService::OrbisUserServiceUserId hostUserId; + Libraries::UserService::OrbisUserServiceUserId visitorUserId; +}; + int PS4_SYSV_ABI sceSharePlayCrashDaemon(); int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfo(OrbisSharePlayConnectionInfo* pInfo); -int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA(); +int PS4_SYSV_ABI sceSharePlayGetCurrentConnectionInfoA(OrbisSharePlayConnectionInfoA* pInfo); int PS4_SYSV_ABI sceSharePlayGetCurrentInfo(); int PS4_SYSV_ABI sceSharePlayGetEvent(); int PS4_SYSV_ABI sceSharePlayInitialize(); diff --git a/src/core/user_manager.h b/src/core/user_manager.h index bf79c8573..4d172f2dc 100644 --- a/src/core/user_manager.h +++ b/src/core/user_manager.h @@ -20,6 +20,11 @@ struct User { std::string shadnet_token = ""; // 2FA/validation token (future use) std::string shadnet_email = ""; // email address (furute use) bool shadnet_enabled = false; // enable shadnet for user + // these are used to populate NP profile fields + std::string np_country = "us"; // ISO 3166-1 alpha-2, e.g. "us", "jp", "gb" + std::string np_language = "en"; // ISO 639-1, e.g. "en", "ja", "fr" + u8 np_age = 30; // 0..127 + std::string np_date_of_birth = "1994-01-01"; // ISO 8601 date "YYYY-MM-DD" }; struct Users { @@ -28,7 +33,8 @@ struct Users { }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(User, user_id, user_color, user_name, player_index, shadnet_npid, shadnet_password, shadnet_token, - shadnet_email, shadnet_enabled) + shadnet_email, shadnet_enabled, np_country, + np_language, np_age, np_date_of_birth) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Users, user, commit_hash) using LoggedInUsers = std::array; diff --git a/src/emulator.cpp b/src/emulator.cpp index 66fe154fa..bfefa3a9e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -34,6 +34,7 @@ #include "core/file_sys/fs.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/libs.h" +#include "core/libraries/np/np_handler.h" #include "core/libraries/np/np_trophy.h" #include "core/libraries/save_data/save_backup.h" #include "core/linker.h" @@ -535,6 +536,7 @@ void Emulator::Restart(std::filesystem::path eboot_path, LOG_INFO(Common, "Restarting the emulator with args: {}", fmt::join(args, " ")); Libraries::SaveData::Backup::StopThread(); + Libraries::Np::NpHandler::GetInstance().Shutdown(); Common::Log::Shutdown(); auto& ipc = IPC::Instance(); diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp new file mode 100644 index 000000000..37c3f18fd --- /dev/null +++ b/src/shadnet/client.cpp @@ -0,0 +1,658 @@ +// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "client.h" +#include "common/logging/log.h" +#include "shadnet.pb.h" + +#ifdef _WIN32 +#pragma comment(lib, "Ws2_32.lib") +static void PlatformInit() { + static bool done = false; + if (!done) { + WSADATA wd; + WSAStartup(MAKEWORD(2, 2), &wd); + done = true; + } +} +#else +static void PlatformInit() {} +#endif + +namespace ShadNet { + +// Build a u32-LE-prefixed proto blob payload for a request packet. +template +static std::vector MakeProtoPayload(const T& msg) { + const std::string serialised = msg.SerializeAsString(); + const u32 len = static_cast(serialised.size()); + std::vector out(4); + out[0] = static_cast(len); + out[1] = static_cast(len >> 8); + out[2] = static_cast(len >> 16); + out[3] = static_cast(len >> 24); + out.insert(out.end(), serialised.begin(), serialised.end()); + return out; +} + +// Read a u32-LE-prefixed proto blob starting at pos in p. +// Returns the raw bytes ready for ParseFromString. +std::string ShadNetClient::ExtractBlob(const std::vector& p, int pos) { + if (pos + 4 > static_cast(p.size())) + return {}; + const u32 len = GetLE32(p.data() + pos); + pos += 4; + if (pos + static_cast(len) > static_cast(p.size())) + return {}; + return std::string(reinterpret_cast(p.data() + pos), len); +} + +ShadNetClient::ShadNetClient() { + PlatformInit(); +} + +ShadNetClient::~ShadNetClient() { + Stop(); +} + +void ShadNetClient::Start(const std::string& host, u16 port, const std::string& npid, + const std::string& password, const std::string& token) { + m_host = host; + m_port = port; + m_npid = npid; + m_password = password; + m_token = token; + m_terminate = false; + m_thread_connect = std::thread(&ShadNetClient::ConnectThread, this); +} + +void ShadNetClient::Stop() { + m_terminate = true; + try { + m_sem_connected.release(); + } catch (...) { + } + try { + m_sem_authenticated.release(); + } catch (...) { + } + { + std::lock_guard lock(m_mutex_send_queue); + m_cv_send_queue.notify_all(); + } + DoDisconnect(); + if (m_thread_connect.joinable()) + m_thread_connect.join(); + if (m_thread_reader.joinable()) + m_thread_reader.join(); + if (m_thread_writer.joinable()) + m_thread_writer.join(); +} + +ShadNetState ShadNetClient::WaitForConnection() { + { + std::lock_guard lock(m_mutex_connected); + if (m_connected) + return ShadNetState::Ok; + } + m_sem_connected.acquire(); + return m_connected ? ShadNetState::Ok : m_state.load(); +} + +ShadNetState ShadNetClient::WaitForAuthenticated() { + { + std::lock_guard lock(m_mutex_authenticated); + if (m_authenticated) + return ShadNetState::Ok; + } + m_sem_authenticated.acquire(); + return m_authenticated ? ShadNetState::Ok : m_state.load(); +} + +bool ShadNetClient::IsConnected() const { + return m_connected.load(); +} +bool ShadNetClient::IsAuthenticated() const { + return m_authenticated.load(); +} +ShadNetState ShadNetClient::GetState() const { + return m_state.load(); +} +const std::string& ShadNetClient::GetAvatarUrl() const { + return m_avatar_url; +} +u64 ShadNetClient::GetUserId() const { + return m_user_id; +} +u32 ShadNetClient::GetAddrLocal() const { + return m_addr_local.load(); +} + +u32 ShadNetClient::GetNumFriends() const { + std::lock_guard lock(m_mutex_friends); + return static_cast(m_friends.size()); +} + +std::optional ShadNetClient::GetFriendNpid(u32 index) const { + std::lock_guard lock(m_mutex_friends); + if (index >= m_friends.size()) + return std::nullopt; + return m_friends[index].npid; +} + +// Threading + +void ShadNetClient::ConnectThread() { + if (!DoConnect()) { + m_sem_connected.release(); + m_sem_authenticated.release(); + return; + } + + m_thread_reader = std::thread(&ShadNetClient::ReaderThread, this); + m_thread_writer = std::thread(&ShadNetClient::WriterThread, this); + m_sem_connected.release(); + + // Build Login request as protobuf + shadnet::LoginRequest req; + req.set_npid(m_npid); + req.set_password(m_password); + if (!m_token.empty()) + req.set_token(m_token); + + const u64 id = m_pkt_counter.fetch_add(1); + if (!SendAll(BuildPacket(CommandType::Login, id, MakeProtoPayload(req)))) { + LOG_ERROR(ShadNet, "ShadNet: Failed to send Login packet"); + m_state = ShadNetState::FailureOther; + return; + } + LOG_INFO(ShadNet, "Login packet sent for '{}'", m_npid); +} + +void ShadNetClient::ReaderThread() { + while (!m_terminate) { + u8 hdr[SHAD_HEADER_SIZE]; + if (!RecvN(hdr, SHAD_HEADER_SIZE)) { + if (!m_terminate) + LOG_WARNING(ShadNet, "Reader header recv failed, disconnecting"); + break; + } + const auto ptype = static_cast(hdr[0]); + const u16 cmd_raw = GetLE16(hdr + 1); + const u32 total_sz = GetLE32(hdr + 3); + const u64 pkt_id = GetLE64(hdr + 7); + + if (total_sz < SHAD_HEADER_SIZE || total_sz > SHAD_MAX_PACKET_SIZE) { + LOG_ERROR(ShadNet, "Corrupt packet (total_sz={})", total_sz); + m_state = ShadNetState::FailureProtocol; + break; + } + std::vector payload; + const u32 payload_sz = total_sz - static_cast(SHAD_HEADER_SIZE); + if (payload_sz > 0) { + payload.resize(payload_sz); + if (!RecvN(payload.data(), payload_sz)) { + if (!m_terminate) + LOG_WARNING(ShadNet, "Reader payload recv failed"); + break; + } + } + DispatchPacket(ptype, cmd_raw, pkt_id, payload); + } + + if (!m_authenticated) { + if (m_state == ShadNetState::Ok) + m_state = ShadNetState::FailureOther; + m_sem_authenticated.release(); + } + m_connected = false; + m_authenticated = false; + LOG_INFO(ShadNet, "ReaderThread exiting"); +} + +void ShadNetClient::WriterThread() { + while (!m_terminate) { + std::unique_lock lock(m_mutex_send_queue); + m_cv_send_queue.wait(lock, [&] { return m_terminate.load() || !m_send_queue.empty(); }); + if (m_terminate) + break; + std::vector> batch; + std::swap(batch, m_send_queue); + lock.unlock(); + for (auto& pkt : batch) { + if (!m_connected) + break; + if (!SendAll(pkt)) { + LOG_ERROR(ShadNet, "WriterThread send failed"); + return; + } + } + } +} + +// Connect / Disconnect + +bool ShadNetClient::DoConnect() { + struct addrinfo hints{}, *res_list = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (::getaddrinfo(m_host.c_str(), std::to_string(m_port).c_str(), &hints, &res_list) != 0 || + !res_list) { + LOG_ERROR(ShadNet, "DNS resolution failed for '{}'", m_host); + m_state = ShadNetState::FailureResolve; + return false; + } + + m_sock = ::socket(res_list->ai_family, SOCK_STREAM, IPPROTO_TCP); + if (m_sock == SHAD_INVALID_SOCK) { + ::freeaddrinfo(res_list); + m_state = ShadNetState::FailureConnect; + return false; + } + + // Use non-blocking connect + select() so we time out instead of hanging + // for the OS default TCP timeout (75 s on Linux, >20 s on Windows). +#ifdef _WIN32 + { + u_long nb = 1; + ::ioctlsocket(m_sock, FIONBIO, &nb); + } +#else + { + int fl = ::fcntl(m_sock, F_GETFL, 0); + ::fcntl(m_sock, F_SETFL, fl | O_NONBLOCK); + } +#endif + + const int cr = ::connect(m_sock, res_list->ai_addr, static_cast(res_list->ai_addrlen)); + ::freeaddrinfo(res_list); + + bool connected = false; +#ifdef _WIN32 + const bool in_progress = (cr < 0 && WSAGetLastError() == WSAEWOULDBLOCK); +#else + const bool in_progress = (cr < 0 && errno == EINPROGRESS); +#endif + if (cr == 0 || in_progress) { + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(m_sock, &wfds); + struct timeval tv{static_cast(SHAD_CONNECT_TIMEOUT_MS / 1000), + static_cast((SHAD_CONNECT_TIMEOUT_MS % 1000) * 1000)}; + if (::select(static_cast(m_sock) + 1, nullptr, &wfds, nullptr, &tv) > 0) { + int err = 0; + socklen_t len = sizeof(err); + ::getsockopt(m_sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &len); + connected = (err == 0); + } + } + if (!connected) { + LOG_ERROR(ShadNet, "connect() timed out or failed for {}:{}", m_host, m_port); + SHAD_CLOSE(m_sock); + m_sock = SHAD_INVALID_SOCK; + m_state = ShadNetState::FailureConnect; + return false; + } + + // Restore blocking mode for RecvN / SendAll +#ifdef _WIN32 + { + u_long nb = 0; + ::ioctlsocket(m_sock, FIONBIO, &nb); + } +#else + { + int fl = ::fcntl(m_sock, F_GETFL, 0); + ::fcntl(m_sock, F_SETFL, fl & ~O_NONBLOCK); + } +#endif + + struct sockaddr_in local{}; + socklen_t alen = sizeof(local); + if (::getsockname(m_sock, reinterpret_cast(&local), &alen) == 0) + m_addr_local.store(local.sin_addr.s_addr); + + LOG_INFO(ShadNet, "TCP connected to {}:{}", m_host, m_port); + + // Apply receive timeout for the ServerInfo handshake. + // Cleared after success so ReaderThread's RecvN blocks indefinitely as intended. +#ifdef _WIN32 + DWORD so_rcv = static_cast(SHAD_CONNECT_TIMEOUT_MS); +#else + struct timeval so_rcv{static_cast(SHAD_CONNECT_TIMEOUT_MS / 1000), + static_cast((SHAD_CONNECT_TIMEOUT_MS % 1000) * 1000)}; +#endif + ::setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&so_rcv), + sizeof(so_rcv)); + + // ServerInfo handshake + u8 hdr[SHAD_HEADER_SIZE]; + if (!RecvN(hdr, SHAD_HEADER_SIZE)) { + LOG_ERROR(ShadNet, "Timeout reading ServerInfo header"); + DoDisconnect(); + m_state = ShadNetState::FailureServerInfo; + return false; + } + if (static_cast(hdr[0]) != PacketType::ServerInfo) { + LOG_ERROR(ShadNet, "Expected ServerInfo, got packet type {:02x}", hdr[0]); + DoDisconnect(); + m_state = ShadNetState::FailureServerInfo; + return false; + } + const u32 total_sz = GetLE32(hdr + 3); + const u32 payload_sz = (total_sz > SHAD_HEADER_SIZE) ? total_sz - SHAD_HEADER_SIZE : 0; + std::vector si_payload(payload_sz); + if (payload_sz > 0 && !RecvN(si_payload.data(), payload_sz)) { + LOG_ERROR(ShadNet, "Timeout reading ServerInfo payload"); + DoDisconnect(); + m_state = ShadNetState::FailureServerInfo; + return false; + } + if (payload_sz >= 4) { + const u32 server_ver = GetLE32(si_payload.data()); + if (server_ver != SHAD_PROTOCOL_VERSION) { + LOG_ERROR(ShadNet, "Protocol version mismatch server={} client={}", server_ver, + SHAD_PROTOCOL_VERSION); + DoDisconnect(); + m_state = ShadNetState::FailureServerInfo; + return false; + } + } + LOG_INFO(ShadNet, "ServerInfo OK (protocol v{})", SHAD_PROTOCOL_VERSION); + + // Clear the receive timeout ReaderThread handles the socket from here. +#ifdef _WIN32 + DWORD no_timeout = 0; +#else + struct timeval no_timeout{0, 0}; +#endif + ::setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&no_timeout), + sizeof(no_timeout)); + + m_connected = true; + return true; +} + +void ShadNetClient::DoDisconnect() { + if (m_sock != SHAD_INVALID_SOCK) { +#ifdef _WIN32 + ::shutdown(m_sock, SD_BOTH); +#else + ::shutdown(m_sock, SHUT_RDWR); +#endif + SHAD_CLOSE(m_sock); + m_sock = SHAD_INVALID_SOCK; + } +} + +bool ShadNetClient::RecvN(u8* buf, u32 n) { + u32 received = 0; + while (received < n) { + if (m_terminate) + return false; + const int r = static_cast(::recv(m_sock, reinterpret_cast(buf + received), + static_cast(n - received), 0)); + if (r <= 0) + return false; + received += static_cast(r); + } + return true; +} + +bool ShadNetClient::SendAll(const std::vector& data) { + std::lock_guard lock(m_mutex_send_direct); + int sent = 0; + const int total = static_cast(data.size()); + while (sent < total) { + const int r = static_cast( + ::send(m_sock, reinterpret_cast(data.data() + sent), total - sent, 0)); + if (r < 0) { + LOG_ERROR(ShadNet, "send() failed"); + return false; + } + sent += r; + } + return true; +} + +std::vector ShadNetClient::BuildPacket(CommandType cmd, u64 id, + const std::vector& payload) const { + const u32 total = static_cast(SHAD_HEADER_SIZE + payload.size()); + std::vector out(SHAD_HEADER_SIZE); + out[0] = static_cast(PacketType::Request); + PutLE16(out, 1, static_cast(cmd)); + PutLE32(out, 3, total); + PutLE64(out, 7, id); + out.insert(out.end(), payload.begin(), payload.end()); + return out; +} + +u64 ShadNetClient::SubmitRequest(CommandType cmd, const std::vector& payload) { + const u64 pkt_id = m_pkt_counter.fetch_add(1); + auto pkt = BuildPacket(cmd, pkt_id, payload); + { + std::lock_guard lock(m_mutex_send_queue); + m_send_queue.push_back(std::move(pkt)); + } + m_cv_send_queue.notify_all(); + return pkt_id; +} + +// Packet dispatch + +void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, + const std::vector& payload) { + switch (type) { + case PacketType::Reply: + switch (static_cast(cmd_raw)) { + case CommandType::Login: + HandleLoginReply(payload); + break; + default: + if (onAsyncReply) { + // Every reply body starts with an ErrorType byte. + ErrorType err = + payload.empty() ? ErrorType::Malformed : static_cast(payload[0]); + std::vector body; + if (payload.size() > 1) { + body.assign(payload.begin() + 1, payload.end()); + } + onAsyncReply(static_cast(cmd_raw), pkt_id, err, body); + } else { + LOG_DEBUG(ShadNet, "Unhandled reply cmd={} pkt_id={}", cmd_raw, pkt_id); + } + break; + } + break; + case PacketType::Notification: + HandleNotification(cmd_raw, payload); + break; + case PacketType::ServerInfo: + LOG_DEBUG(ShadNet, "ServerInfo update received"); + break; + case PacketType::Request: + LOG_WARNING(ShadNet, "Unexpected Request from server"); + break; + } +} + +// Login reply + +void ShadNetClient::HandleLoginReply(const std::vector& payload) { + LoginResult res; + + if (payload.empty()) { + res.error = ErrorType::Malformed; + LOG_ERROR(ShadNet, "Empty Login reply"); + } else { + res.error = static_cast(payload[0]); + + if (res.error == ErrorType::NoError) { + // payload[0] = ErrorType byte + // payload[1..] = u32 LE blob size + LoginReply proto bytes + shadnet::LoginReply pb; + const std::string blob = ExtractBlob(payload, 1); + if (!blob.empty() && pb.ParseFromString(blob)) { + res.avatarUrl = pb.avatar_url(); + res.userId = pb.user_id(); + for (const auto& f : pb.friends()) { + FriendEntry fe; + fe.npid = f.npid(); + fe.online = f.online(); + res.friends.push_back(std::move(fe)); + } + for (const auto& n : pb.friend_requests_sent()) + res.requestsSent.push_back(n); + for (const auto& n : pb.friend_requests_received()) + res.requestsReceived.push_back(n); + for (const auto& n : pb.blocked()) + res.blocked.push_back(n); + + m_avatar_url = res.avatarUrl; + m_user_id = res.userId; + { + std::lock_guard lock(m_mutex_friends); + m_friends = res.friends; + } + m_authenticated = true; + m_sem_authenticated.release(); + LOG_INFO(ShadNet, "Logged in npid='{}' userId={} friends={}", m_npid, m_user_id, + m_friends.size()); + } else { + res.error = ErrorType::Malformed; + LOG_ERROR(ShadNet, "Failed to parse LoginReply proto"); + m_state = ShadNetState::FailureProtocol; + m_sem_authenticated.release(); + DoDisconnect(); + } + } else { + switch (res.error) { + case ErrorType::LoginAlreadyLoggedIn: + m_state = ShadNetState::FailureAlreadyIn; + break; + case ErrorType::LoginInvalidUsername: + m_state = ShadNetState::FailureUsername; + break; + case ErrorType::LoginInvalidPassword: + m_state = ShadNetState::FailurePassword; + break; + case ErrorType::LoginInvalidToken: + m_state = ShadNetState::FailureToken; + break; + default: + m_state = ShadNetState::FailureAuth; + break; + } + LOG_ERROR(ShadNet, "Login rejected error code {}", static_cast(res.error)); + m_sem_authenticated.release(); + DoDisconnect(); + } + } + + if (onLoginResult) + onLoginResult(res); +} + +// Notifications + +void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& payload) { + // Notification payload = u32 LE blob size + proto bytes + const std::string blob = ExtractBlob(payload, 0); + if (blob.empty()) { + LOG_WARNING(ShadNet, "Empty notification payload type={}", cmd_raw); + return; + } + + switch (static_cast(cmd_raw)) { + case NotificationType::FriendQuery: { + shadnet::NotifyFriendQuery pb; + if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "FriendQuery parse error"); + break; + } + NotifyFriendQuery n; + n.fromNpid = pb.from_npid(); + LOG_DEBUG(ShadNet, "FriendQuery from '{}'", n.fromNpid); + if (onFriendQuery) + onFriendQuery(n); + break; + } + case NotificationType::FriendNew: { + shadnet::NotifyFriendNew pb; + if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "FriendNew parse error"); + break; + } + NotifyFriendNew n; + n.npid = pb.npid(); + n.online = pb.online(); + LOG_DEBUG(ShadNet, "FriendNew '{}' ({})", n.npid, n.online ? "online" : "offline"); + if (onFriendNew) + onFriendNew(n); + break; + } + case NotificationType::FriendLost: { + shadnet::NotifyFriendLost pb; + if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "FriendLost parse error"); + break; + } + NotifyFriendLost n; + n.npid = pb.npid(); + LOG_DEBUG(ShadNet, "FriendLost '{}'", n.npid); + if (onFriendLost) + onFriendLost(n); + break; + } + case NotificationType::FriendStatus: { + shadnet::NotifyFriendStatus pb; + if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "FriendStatus parse error"); + break; + } + NotifyFriendStatus n; + n.npid = pb.npid(); + n.online = pb.online(); + n.timestamp = pb.timestamp(); + LOG_DEBUG(ShadNet, "FriendStatus '{}' is {}", n.npid, n.online ? "online" : "offline"); + if (onFriendStatus) + onFriendStatus(n); + break; + } + default: + LOG_DEBUG(ShadNet, "Unknown notification type {}", cmd_raw); + break; + } +} + +void ShadNetClient::PutLE16(std::vector& b, size_t off, u16 v) { + b[off] = static_cast(v); + b[off + 1] = static_cast(v >> 8); +} +void ShadNetClient::PutLE32(std::vector& b, size_t off, u32 v) { + b[off] = static_cast(v); + b[off + 1] = static_cast(v >> 8); + b[off + 2] = static_cast(v >> 16); + b[off + 3] = static_cast(v >> 24); +} +void ShadNetClient::PutLE64(std::vector& b, size_t off, u64 v) { + for (int i = 0; i < 8; ++i) + b[off + i] = static_cast(v >> (8 * i)); +} +u16 ShadNetClient::GetLE16(const u8* p) { + return static_cast(p[0]) | (static_cast(p[1]) << 8); +} +u32 ShadNetClient::GetLE32(const u8* p) { + return static_cast(p[0]) | (static_cast(p[1]) << 8) | (static_cast(p[2]) << 16) | + (static_cast(p[3]) << 24); +} +u64 ShadNetClient::GetLE64(const u8* p) { + u64 v = 0; + for (int i = 0; i < 8; ++i) + v |= static_cast(p[i]) << (8 * i); + return v; +} + +} // namespace ShadNet diff --git a/src/shadnet/client.h b/src/shadnet/client.h new file mode 100644 index 000000000..3499f1e19 --- /dev/null +++ b/src/shadnet/client.h @@ -0,0 +1,272 @@ +// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/types.h" +#ifdef _WIN32 +#include +#include +using ShadSocketHandle = SOCKET; +static constexpr ShadSocketHandle SHAD_INVALID_SOCK = INVALID_SOCKET; +#define SHAD_CLOSE(s) ::closesocket(s) +#else +#include +#include +#include +#include +#include +#include +#include +using ShadSocketHandle = int; +static constexpr ShadSocketHandle SHAD_INVALID_SOCK = -1; +#define SHAD_CLOSE(s) ::close(s) +#endif + +namespace ShadNet { + +// Protocol constants +static constexpr u32 SHAD_HEADER_SIZE = 15; +static constexpr u32 SHAD_CONNECT_TIMEOUT_MS = 10000; // 10 second connect/handshake timeout +static constexpr u32 SHAD_PROTOCOL_VERSION = 1; +static constexpr u32 SHAD_MAX_PACKET_SIZE = 0x800000; // 8 MiB + +// Protocol enumerations (must match shadnet server protocol.h) +enum class PacketType : u8 { + Request = 0, + Reply = 1, + Notification = 2, + ServerInfo = 3, +}; + +enum class CommandType : u16 { + Login = 0, + Terminate = 1, + Create = 2, + Delete = 3, + SendToken = 4, + SendResetToken = 5, + ResetPassword = 6, + ResetState = 7, + AddFriend = 8, + RemoveFriend = 9, + AddBlock = 10, + RemoveBlock = 11, + // 12–29: room/lobby/ticket NOT implemented in shadNet + GetBoardInfos = 30, + RecordScore = 31, + RecordScoreData = 32, + GetScoreData = 33, + GetScoreRange = 34, + GetScoreFriends = 35, + GetScoreNpid = 36, + GetScoreAccountId = 37, + GetScoreGameDataByAccId = 38, +}; + +enum class NotificationType : u16 { + FriendQuery = 5, + FriendNew = 6, + FriendLost = 7, + FriendStatus = 8, +}; + +enum class ErrorType : uint8_t { + NoError = 0, + Malformed = 1, + Invalid = 2, + InvalidInput = 3, + TooSoon = 4, + LoginError = 5, + LoginAlreadyLoggedIn = 6, + LoginInvalidUsername = 7, + LoginInvalidPassword = 8, + LoginInvalidToken = 9, + CreationError = 10, + CreationExistingUsername = 11, + CreationBannedEmailProvider = 12, + CreationExistingEmail = 13, + RoomMissing = 14, + RoomAlreadyJoined = 15, + RoomFull = 16, + RoomPasswordMismatch = 17, + RoomPasswordMissing = 18, + RoomGroupNoJoinLabel = 19, + RoomGroupFull = 20, + RoomGroupJoinLabelNotFound = 21, + RoomGroupMaxSlotMismatch = 22, + Unauthorized = 23, + DbFail = 24, + EmailFail = 25, + NotFound = 26, + Blocked = 27, + AlreadyFriend = 28, + ScoreNotBest = 29, + ScoreInvalid = 30, + ScoreHasData = 31, + CondFail = 32, + Unsupported = 33, +}; + +enum class ShadNetState { + Ok, + FailureInput, + FailureResolve, + FailureConnect, + FailureServerInfo, + FailureAuth, + FailureAlreadyIn, + FailureUsername, + FailurePassword, + FailureToken, + FailureProtocol, + FailureOther, +}; + +// Callback data structures + +struct FriendEntry { + std::string npid; + bool online = false; +}; + +struct LoginResult { + ErrorType error = ErrorType::Malformed; + std::string avatarUrl; + u64 userId = 0; + std::vector friends; + std::vector requestsSent; + std::vector requestsReceived; + std::vector blocked; +}; + +struct NotifyFriendQuery { + std::string fromNpid; +}; +struct NotifyFriendNew { + std::string npid; + bool online = false; +}; +struct NotifyFriendLost { + std::string npid; +}; +struct NotifyFriendStatus { + std::string npid; + bool online = false; + u64 timestamp = 0; +}; + +// ShadNetClient + +class ShadNetClient { +public: + ShadNetClient(); + ~ShadNetClient(); + ShadNetClient(const ShadNetClient&) = delete; + ShadNetClient& operator=(const ShadNetClient&) = delete; + + void Start(const std::string& host, u16 port, const std::string& npid, + const std::string& password, const std::string& token = {}); + void Stop(); + + ShadNetState WaitForConnection(); + ShadNetState WaitForAuthenticated(); + + bool IsConnected() const; + bool IsAuthenticated() const; + ShadNetState GetState() const; + + const std::string& GetAvatarUrl() const; + u64 GetUserId() const; + u32 GetAddrLocal() const; + u32 GetNumFriends() const; + std::optional GetFriendNpid(u32 index) const; + + // Callbacks + std::function onLoginResult; + std::function onFriendQuery; + std::function onFriendNew; + std::function onFriendLost; + std::function onFriendStatus; + // Async reply callback. + // cmd —command this reply is for (matches the request's cmd) + // pkt_id —packet id echoed back from the original request header + // error —ErrorType byte that prefixes every reply body + // body —reply payload AFTER the error byte (may be empty) + std::function& body)> + onAsyncReply; + + // Submit a Request packet for async processing. + // Allocates a packet id, builds the packet, pushes it to the writer queue. + // Returns the packet id so callers can correlate the eventual reply. + u64 SubmitRequest(CommandType cmd, const std::vector& payload); + +private: + void ConnectThread(); + void ReaderThread(); + void WriterThread(); + bool DoConnect(); + void DoDisconnect(); + bool RecvN(u8* buf, u32 n); + bool SendAll(const std::vector& data); + std::vector BuildPacket(CommandType cmd, u64 id, const std::vector& payload) const; + void DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, const std::vector& payload); + void HandleLoginReply(const std::vector& payload); + void HandleNotification(u16 cmd_raw, const std::vector& payload); + + // Helper: read a u32-LE-prefixed proto blob from a byte vector at pos. + static std::string ExtractBlob(const std::vector& p, int pos); + + static void PutLE16(std::vector& b, size_t off, u16 v); + static void PutLE32(std::vector& b, size_t off, u32 v); + static void PutLE64(std::vector& b, size_t off, u64 v); + static u16 GetLE16(const u8* p); + static u32 GetLE32(const u8* p); + static u64 GetLE64(const u8* p); + + ShadSocketHandle m_sock = SHAD_INVALID_SOCK; + std::string m_host; + u16 m_port = 31313; + std::string m_npid; + std::string m_password; + std::string m_token; + + std::atomic m_terminate{false}; + std::atomic m_connected{false}; + std::atomic m_authenticated{false}; + std::atomic m_state{ShadNetState::Ok}; + + std::binary_semaphore m_sem_connected{0}; + std::binary_semaphore m_sem_authenticated{0}; + std::mutex m_mutex_connected; + std::mutex m_mutex_authenticated; + + std::thread m_thread_connect; + std::thread m_thread_reader; + std::thread m_thread_writer; + + std::mutex m_mutex_send_direct; + std::mutex m_mutex_send_queue; + std::condition_variable m_cv_send_queue; + std::vector> m_send_queue; + + std::string m_avatar_url; + u64 m_user_id = 0; + std::atomic m_addr_local{0}; + + mutable std::mutex m_mutex_friends; + std::vector m_friends; + + std::atomic m_pkt_counter{1}; +}; + +} // namespace ShadNet diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto new file mode 100644 index 000000000..d4a0471f3 --- /dev/null +++ b/src/shadnet/shadnet.proto @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright 2019-2026 rpcsn Project +// SPDX-FileCopyrightText: Copyright 2026 shadNet Project +// SPDX-License-Identifier: GPL-2.0-or-later + +syntax = "proto3"; + +package shadnet; + +option optimize_for = LITE_RUNTIME; + +// Account + +message LoginRequest { + string npid = 1; + string password = 2; + string token = 3; +} + +message FriendEntry { + string npid = 1; + bool online = 2; + bytes presence = 3; +} + +message LoginReply { + string avatar_url = 1; + uint64 user_id = 2; + repeated FriendEntry friends = 3; + repeated string friend_requests_sent = 4; + repeated string friend_requests_received = 5; + repeated string blocked = 6; +} + +message RegistrationRequest { + string npid = 1; + string password = 2; + string avatar_url = 3; // optional; server uses default if empty + string email = 4; + string secret_key = 5; // optional; must match server config if set +} + +// Friend / block commands +// Shared by AddFriend, RemoveFriend, AddBlock, RemoveBlock. + +message FriendCommandRequest { + string npid = 1; // target Online ID +} + +// Notifications + +// NotificationType::FriendQuery (5) +message NotifyFriendQuery { + string from_npid = 1; +} + +// NotificationType::FriendNew (6) +message NotifyFriendNew { + string npid = 1; + bool online = 2; +} + +// NotificationType::FriendLost (7) +message NotifyFriendLost { + string npid = 1; +} + +// NotificationType::FriendStatus (8) +message NotifyFriendStatus { + string npid = 1; + bool online = 2; + uint64 timestamp = 3; // nanoseconds since Unix epoch +} + +// Score: board configuration + +message BoardInfo { + uint32 rankLimit = 1; + uint32 updateMode = 2; // 0=NORMAL_UPDATE, 1=FORCE_UPDATE + uint32 sortMode = 3; // 0=DESCENDING, 1=ASCENDING + uint32 uploadNumLimit = 4; + uint64 uploadSizeLimit = 5; +} + +// Score: requests + +message RecordScoreRequest { + uint32 boardId = 1; + int32 pcId = 2; + int64 score = 3; + string comment = 4; + bytes data = 5; // optional inline game-info blob +} + +message RecordScoreGameDataRequest { + uint32 boardId = 1; + int32 pcId = 2; + int64 score = 3; +} + +message GetScoreGameDataRequest { + uint32 boardId = 1; + string npId = 2; + int32 pcId = 3; +} + +message GetScoreRangeRequest { + uint32 boardId = 1; + uint32 startRank = 2; + uint32 numRanks = 3; + bool withComment = 4; + bool withGameInfo = 5; +} + +message ScoreNpIdPcId { + string npid = 1; + int32 pcId = 2; +} + +message GetScoreNpIdRequest { + uint32 boardId = 1; + repeated ScoreNpIdPcId npids = 2; + bool withComment = 3; + bool withGameInfo = 4; +} + +message GetScoreFriendsRequest { + uint32 boardId = 1; + bool includeSelf = 2; + uint32 max = 3; + bool withComment = 4; + bool withGameInfo = 5; +} + +message ScoreAccountIdPcId { + int64 accountId = 1; + int32 pcId = 2; +} + +message GetScoreAccountIdRequest { + uint32 boardId = 1; + repeated ScoreAccountIdPcId ids = 2; + bool withComment = 3; + bool withGameInfo = 4; +} + +message GetScoreGameDataByAccountIdRequest { + uint32 boardId = 1; + int64 accountId = 2; + int32 pcId = 3; +} + +// Score: responses + +message ScoreRankData { + string npId = 1; + int32 pcId = 2; + uint32 rank = 3; + int64 score = 4; + bool hasGameData = 5; + uint64 recordDate = 6; + int64 accountId = 7; +} + +message ScoreInfo { + bytes data = 1; +} + +message GetScoreResponse { + repeated ScoreRankData rankArray = 1; + repeated string commentArray = 2; + repeated ScoreInfo infoArray = 3; + uint64 lastSortDate = 4; + uint32 totalRecord = 5; +}