Online play, Add to Steam (1.6)

Online play through shadNet and The Hunter's Dream, built from the network and PSN libraries
of shadp2p (github.com/Wozzardman/shadp2p): sign-in, matchmaking, signaling, WebAPI and HTTPS.
With BB_ONLINE=1 the network and PSN calls go to these libraries instead of the offline stubs;
trophies, voice and the store dialogs stay in the runtime. run.py points the game's own
server at The Hunter's Dream and sends summon signs to the shadNet server when it has the
Bloodborne summon service.

Launcher: Online co-op page (game server, co-op server, account, connection test), Play online
and Add to Steam on the Play page, a warning when an account is set but online play is off,
last_run.log written for every run, author line, translations for the new texts.
This commit is contained in:
Supermedo committed 2026-10-08 23:15:26 +02:00
1 parent ebff3d66ff
commit e076119653
142 files changed
+71651 -15

No files matched your search

+27 -1
View File
@@ -15,6 +15,9 @@ start `Bloodborne.exe`, pick your game folder and press **PLAY**.
## Features
- **Online play (beta):** messages, bloodstains and wandering ghosts from other hunters through
[The Hunter's Dream](https://thehuntersdream.com), and bells and summons through a shadNet
server (shadPS4's public one by default).
- **Launcher with every setting in one window**, in 13 languages: English, Arabic, Russian,
Spanish, Portuguese, French, German, Italian, Polish, Turkish, Chinese, Japanese, Korean.
- **Updates from the launcher:** when a new version is out it tells you, and **Update**
@@ -29,8 +32,9 @@ start `Bloodborne.exe`, pick your game folder and press **PLAY**.
- **Game effects** on and off: chromatic aberration, depth of field, motion blur, SSAO, the
game's own AA, dynamic light shadows, screen-space reflections, model detail.
- **Mods and third-party patches**, loaded without changing your game files.
- **Add to Steam** puts the game in your Steam library in one click.
- **`Play Bloodborne.exe`** starts the game straight away with your saved settings (good for a
desktop shortcut or Steam).
desktop shortcut).
- **Controller and keyboard**, an in-game settings menu (Insert or L3+R3), name entry on
screen, a desktop shortcut, and a button to clear the shader cache.
@@ -55,11 +59,25 @@ In the game, **Insert** (or **L3+R3** on a controller) opens the settings menu.
Keyboard: WASD move, arrows camera, Space Cross, Left Shift Circle, E Square, Q Triangle,
1/3 L1/R1, R/F L2/R2, Z/C L3/R3, I/K/J/L d-pad, Enter Options, Tab touchpad.
## Playing online (beta)
1. Register a shadNet account at [shadnet.shadps4.net](https://shadnet.shadps4.net).
2. In the launcher, open **Online co-op**, tick **Play online** and enter your account name
(not your email) and password.
3. Press **Test connection**: all three lines should say "reachable". Then press **PLAY** and
choose online at the title screen. The Hunter's Dream welcome message means you are online.
Everyone you want to play with needs game version 1.09 and the same co-op server. A private
shadNet server (from the [shadp2p](https://github.com/Wozzardman/shadp2p) project) works too:
put its address in **Server address**.
## Known issues
- The character preview on the character creation screen stays empty. The character is
created correctly and looks right in the game.
- Some AMD graphics cards still crash when the game world loads; fixes are in progress.
- Online play is new. Messages, bloodstains and ghosts work; summoning other players with the
bells has not been confirmed yet. Please report how it goes on Discord.
- Above about 120 FPS the game's movement slows down (a limit of the game itself): keep the
frame cap at 120 or lower.
@@ -81,6 +99,11 @@ Built on [bbport](https://github.com/deadinside28/bloodborne_pc), the native Lin
Bloodborne by deadinside28, and on the [shadPS4](https://github.com/shadps4-emu/shadPS4)
renderer. The original README is in [docs/original-readme](docs/original-readme/README.md).
Online play uses the network and PSN code of [shadp2p](https://github.com/Wozzardman/shadp2p)
by Wozzardman (the shadPS4 Bloodborne co-op project), the shadNet server of the
[shadPS4](https://shadps4.net) team, and [The Hunter's Dream](https://thehuntersdream.com) server
by droogie and its team.
Also used: [FSR-Vulkan](https://github.com/FireBurn/FSR-Vulkan) and the AMD FidelityFX SDK
(FSR), FSR 4 assets from [FireBurn/Q2RTX](https://github.com/FireBurn/Q2RTX), the DLSS bridge
adapted from [IFreemz/shadPS4-Bloodborne-DLSS-FSR](https://github.com/IFreemz/shadPS4-Bloodborne-DLSS-FSR),
@@ -89,6 +112,9 @@ the [NVIDIA DLSS SDK](https://github.com/NVIDIA/DLSS) (`nvngx_dlss.dll` under NV
[FFmpeg](https://ffmpeg.org), [Dear ImGui](https://github.com/ocornut/imgui),
[sirit](https://github.com/shadps4-emu/sirit), [magic_enum](https://github.com/Neargye/magic_enum),
[miniz](https://github.com/richgel999/miniz), [xbyak](https://github.com/herumi/xbyak),
[wepoll](https://github.com/piscisaureus/wepoll), [cpp-httplib](https://github.com/yhirose/cpp-httplib),
[Protocol Buffers](https://protobuf.dev), [OpenSSL](https://www.openssl.org),
[miniupnpc](https://miniupnp.tuxfamily.org), [JSON for Modern C++](https://github.com/nlohmann/json),
[Vulkan Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator),
[MSYS2](https://www.msys2.org) / [LLVM](https://llvm.org), [PyInstaller](https://pyinstaller.org)
and [Pillow](https://python-pillow.org). Game patches by Kyo, Lance McDonald, auser1337,
+46
View File
@@ -155,6 +155,52 @@ add_library(bbgpu ${BBGPU_KIND} ${VIDEO_CORE} ${RECOMPILER} ${COMMON} ${SHIM_SOU
shadps4/core/libraries/kernel/equeue.cpp ${AVPLAYER}
shadps4/core/libraries/videodec/video_utils.cpp)
add_dependencies(bbgpu host_shaders)
# bbport co-op: PS4 network and PSN libraries with the shadNet client, from shadp2p
# (github.com/Wozzardman/shadp2p, GPL-2.0), for private Bloodborne co-op servers.
find_package(Protobuf CONFIG REQUIRED)
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}/shadps4/shadnet"
"--cpp_out=${SHADNET_PROTO_OUT}" "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shadps4/shadnet/shadnet.proto")
file(GLOB_RECURSE BBNET_SOURCES CONFIGURE_DEPENDS shadps4/core/libraries/network/*.cpp
shadps4/core/libraries/np/*.cpp shadps4/shadnet/*.cpp)
# Trophies and the system dialogs stay in the C runtime; their shadPS4 versions need the
# emulator's UI layers.
list(FILTER BBNET_SOURCES EXCLUDE REGEX
"np_trophy|trophy_ui|np_sns_facebook_dialog|np_profile_dialog/|np_commerce/")
find_package(nlohmann_json CONFIG REQUIRED)
add_library(bbnet STATIC ${BBNET_SOURCES} "${SHADNET_PROTO_OUT}/shadnet.pb.cc" bbnet/bbnet_glue.cpp)
target_include_directories(bbnet PRIVATE shim shadps4 shadps4/shadnet "${SHADNET_PROTO_OUT}"
third_party/imgui third_party/httplib)
target_link_libraries(bbnet PRIVATE nlohmann_json::nlohmann_json)
# HTTPS (the community game server only speaks TLS) and gzip replies in cpp-httplib.
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
target_compile_definitions(bbnet PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT CPPHTTPLIB_ZLIB_SUPPORT)
target_link_libraries(bbnet PRIVATE OpenSSL::SSL OpenSSL::Crypto ZLIB::ZLIB)
if (WIN32)
target_sources(bbnet PRIVATE third_party/wepoll/wepoll.c)
target_include_directories(bbnet PRIVATE third_party/wepoll)
find_path(MINIUPNPC_INCLUDE miniupnpc.h PATH_SUFFIXES miniupnpc REQUIRED)
find_library(MINIUPNPC_LIBRARY NAMES libminiupnpc.a miniupnpc REQUIRED)
target_include_directories(bbnet PRIVATE "${MINIUPNPC_INCLUDE}")
target_compile_definitions(bbnet PRIVATE MINIUPNP_STATICLIB)
target_link_libraries(bbnet PRIVATE "${MINIUPNPC_LIBRARY}")
endif()
target_compile_definitions(bbnet PRIVATE BOOST_ASIO_STANDALONE NDEBUG)
target_compile_options(bbnet PRIVATE -Wno-deprecated-declarations -Wno-missing-field-initializers)
target_link_libraries(bbnet PUBLIC Vulkan::Headers fmt::fmt Boost::headers magic_enum::magic_enum
protobuf::libprotobuf)
if (WIN32)
target_compile_definitions(bbnet PUBLIC NOMINMAX WIN32_LEAN_AND_MEAN)
target_compile_options(bbnet PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-include ${CMAKE_CURRENT_SOURCE_DIR}/shim/bb_win_compat.h>)
target_link_libraries(bbnet PUBLIC ws2_32 iphlpapi bcrypt crypt32)
endif()
target_link_libraries(bbgpu PUBLIC bbnet)
# In-game settings menu: Dear ImGui (third_party/imgui, MIT) with its Vulkan backend, and an
# embedded Cyrillic font (third_party/fonts).
target_sources(bbgpu PRIVATE third_party/imgui/imgui.cpp third_party/imgui/imgui_draw.cpp
+2
View File
@@ -16,6 +16,8 @@ typedef struct {
} BbGpuConfig;
/* Registers kernel event queues (needed with or without graphics). */
void bbgpu_register_kernel(void);
/* Co-op: the game folder and param.sfo identity for the network and PSN libraries. */
void bbnet_configure(const char *app0, const char *serial, const char *title, const char *app_ver);
/* Creates window, Vulkan device, presenter and GPU command processor. */
int bbgpu_init(const BbGpuConfig *config);
/* Function for an imported NID ("NID#lib#mod"), or 0 when the GPU library does not provide it. */
+476
View File
@@ -0,0 +1,476 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// bbport co-op: what the network and NP libraries from shadp2p need from the rest of
// shadPS4, implemented on top of the bbport runtime, and their registration.
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <unistd.h>
#endif
#include "common/elf_info.h"
#include "common/types.h"
#include "core/emulator_settings.h"
#include "core/bloodborne_re.h"
#include "core/debugger.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/systemservice.h"
#include "core/libraries/system/userservice.h"
#include "core/user_manager.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/shadnet_notifications_layer.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/network/ssl.h"
#include "core/libraries/network/ssl2.h"
#include "core/libraries/np/np_auth.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_party.h"
#include "core/libraries/np/np_score/np_score.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_tus.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
extern "C" {
int32_t runtime_thread_spawn(void** out, void* PS4_SYSV_ABI (*entry)(void*), void* argument,
uint64_t stack, const char* name);
int32_t runtime_thread_join_spawned(void* thread, void** result);
}
// ---- Kernel ---------------------------------------------------------------------------------
namespace Libraries::Kernel {
struct BbNetMutex {
std::recursive_mutex mutex;
bool recursive{};
int depth{};
};
struct BbNetMutexAttr {
PthreadMutexType type{PthreadMutexType::ErrorCheck};
};
struct BbNetCond {
std::condition_variable_any cond;
};
struct BbNetCondAttr {};
struct BbNetThreadAttr {
u64 stack{};
};
s32 ErrnoToSceKernelError(s32 e) {
return e > 0 && e <= 0xffff ? s32(0x80020000u | u32(e)) : e;
}
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret) {
if (!ret) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
std::memset(ret->text_representation, 0, sizeof(ret->text_representation));
std::snprintf(ret->text_representation, sizeof(ret->text_representation), "%s", "11.000.000");
ret->hex_representation = 0x11000001;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelClockGettime(u32 clock_id, OrbisKernelTimespec* tp) {
if (!tp) {
return ORBIS_KERNEL_ERROR_EFAULT;
}
s64 ns;
if (clock_id == ORBIS_CLOCK_REALTIME || clock_id == ORBIS_CLOCK_REALTIME_PRECISE ||
clock_id == ORBIS_CLOCK_REALTIME_FAST || clock_id == ORBIS_CLOCK_SECOND) {
ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
} else {
ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
}
tp->tv_sec = ns / 1000000000;
tp->tv_nsec = ns % 1000000000;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32, s32,
const char*) {
if (!addr_in_out || !len) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
void* memory = std::calloc(1, len);
if (!memory) {
return ORBIS_KERNEL_ERROR_ENOMEM;
}
*addr_in_out = memory;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64) {
std::free(addr);
return ORBIS_OK;
}
int posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) {
if (!attr) return 22;
*attr = new BbNetMutexAttr;
return 0;
}
int posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
if (!attr || !*attr) return 22;
(*attr)->type = type;
return 0;
}
int posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) {
if (!attr) return 22;
delete *attr;
*attr = nullptr;
return 0;
}
int scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* attr, const char*) {
if (!mutex) return 22;
*mutex = new BbNetMutex;
(*mutex)->recursive = attr && *attr && (*attr)->type == PthreadMutexType::Recursive;
return 0;
}
int posix_pthread_mutex_destroy(PthreadMutexT* mutex) {
if (!mutex || !*mutex) return 22;
delete *mutex;
*mutex = nullptr;
return 0;
}
int posix_pthread_mutex_lock(PthreadMutexT* mutex) {
if (!mutex || !*mutex) return 22;
(*mutex)->mutex.lock();
if (++(*mutex)->depth > 1 && !(*mutex)->recursive) {
--(*mutex)->depth;
(*mutex)->mutex.unlock();
return 11; // EDEADLK
}
return 0;
}
int posix_pthread_mutex_trylock(PthreadMutexT* mutex) {
if (!mutex || !*mutex) return 22;
if (!(*mutex)->mutex.try_lock()) return 16; // EBUSY
if (++(*mutex)->depth > 1 && !(*mutex)->recursive) {
--(*mutex)->depth;
(*mutex)->mutex.unlock();
return 16;
}
return 0;
}
int posix_pthread_mutex_unlock(PthreadMutexT* mutex) {
if (!mutex || !*mutex) return 22;
--(*mutex)->depth;
(*mutex)->mutex.unlock();
return 0;
}
int posix_pthread_condattr_init(PthreadCondAttrT* attr) {
if (!attr) return 22;
*attr = new BbNetCondAttr;
return 0;
}
int posix_pthread_condattr_destroy(PthreadCondAttrT* attr) {
if (!attr) return 22;
delete *attr;
*attr = nullptr;
return 0;
}
int scePthreadCondInit(PthreadCondT* cond, const PthreadCondAttrT*, const char*) {
if (!cond) return 22;
*cond = new BbNetCond;
return 0;
}
int posix_pthread_cond_destroy(PthreadCondT* cond) {
if (!cond || !*cond) return 22;
delete *cond;
*cond = nullptr;
return 0;
}
int posix_pthread_cond_signal(PthreadCondT* cond) {
if (!cond || !*cond) return 22;
(*cond)->cond.notify_one();
return 0;
}
namespace {
// The caller holds the mutex once; the wait releases and retakes it like pthread_cond_wait.
struct HeldMutex {
BbNetMutex* mutex;
void lock() {
mutex->mutex.lock();
++mutex->depth;
}
void unlock() {
--mutex->depth;
mutex->mutex.unlock();
}
};
} // namespace
int posix_pthread_cond_wait(PthreadCondT* cond, PthreadMutexT* mutex) {
if (!cond || !*cond || !mutex || !*mutex) return 22;
HeldMutex held{*mutex};
(*cond)->cond.wait(held);
return 0;
}
int posix_pthread_cond_reltimedwait_np(PthreadCondT* cond, PthreadMutexT* mutex, u64 usec) {
if (!cond || !*cond || !mutex || !*mutex) return 22;
HeldMutex held{*mutex};
const auto status = (*cond)->cond.wait_for(held, std::chrono::microseconds(usec));
return status == std::cv_status::timeout ? 60 : 0; // ETIMEDOUT
}
int posix_pthread_attr_init(PthreadAttrT* attr) {
if (!attr) return 22;
*attr = new BbNetThreadAttr;
return 0;
}
int posix_pthread_attr_destroy(PthreadAttrT* attr) {
if (!attr) return 22;
delete *attr;
*attr = nullptr;
return 0;
}
int posix_pthread_attr_setstacksize(PthreadAttrT* attr, size_t stack_size) {
if (!attr || !*attr) return 22;
(*attr)->stack = stack_size;
return 0;
}
int posix_pthread_attr_setinheritsched(PthreadAttrT*, int) {
return 0;
}
int posix_pthread_attr_setschedpolicy(PthreadAttrT*, SchedPolicy) {
return 0;
}
int posix_pthread_attr_setschedparam(PthreadAttrT*, const SchedParam*) {
return 0;
}
int scePthreadAttrSetaffinity(PthreadAttrT*, u64) {
return 0;
}
int posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT* attr,
PthreadEntryFunc start, void* arg, const char* name) {
if (!thread || !start) return 22;
const u64 stack = attr && *attr ? (*attr)->stack : 0;
return runtime_thread_spawn(thread, start, arg, stack, name ? name : "np");
}
int posix_pthread_join(PthreadT thread, void** ret) {
return runtime_thread_join_spawned(thread, ret);
}
} // namespace Libraries::Kernel
// ---- User and system service, debugger, Bloodborne helpers -------------------------------------
namespace Libraries::UserService {
s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
if (!user_id) {
return s32(0x80960009); // SCE_USER_SERVICE_ERROR_INVALID_ARGUMENT
}
*user_id = UserManager::RuntimeUserId;
return ORBIS_OK;
}
} // namespace Libraries::UserService
namespace Libraries::SystemService {
void PushSystemServiceEvent(const OrbisSystemServiceEvent& event) {
std::printf("Co-op: system service event %#x not delivered (invitations are not ported)\n",
unsigned(event.event_type));
}
} // namespace Libraries::SystemService
namespace Core::Debugger {
s32 GetCurrentPid() {
#ifdef _WIN32
return s32(GetCurrentProcessId());
#else
return s32(getpid());
#endif
}
} // namespace Core::Debugger
namespace Core::Bloodborne {
namespace {
std::mutex placement_mutex;
std::optional<std::string> placement;
} // namespace
std::optional<std::string> GetSeamlessHostPlacementHeader() {
std::scoped_lock lock{placement_mutex};
return placement;
}
bool SetSeamlessHostPlacementHeader(std::string_view value) {
if (value.empty() || value.size() > 4096) {
return false;
}
std::scoped_lock lock{placement_mutex};
placement = std::string{value};
return true;
}
void ClearSeamlessHostPlacementHeader() {
std::scoped_lock lock{placement_mutex};
placement.reset();
}
void TraceMatching2LeaveRoom(std::uintptr_t, std::uint64_t) {}
} // namespace Core::Bloodborne
// ---- Notifications (log only for now) ------------------------------------------------------------
namespace ImGui::ShadNetNotify {
namespace {
std::mutex history_mutex;
std::vector<HistoryEntry> history;
std::size_t unread{};
} // namespace
KindDisplay DisplayOf(Kind kind) {
switch (kind) {
case Kind::FriendRequest:
return {"Friend request", {0.9f, 0.8f, 0.3f, 1.0f}};
case Kind::FriendNew:
return {"Friend", {0.4f, 0.9f, 0.4f, 1.0f}};
case Kind::FriendLost:
return {"Friend", {0.9f, 0.4f, 0.4f, 1.0f}};
case Kind::Online:
return {"Online", {0.4f, 0.8f, 0.9f, 1.0f}};
default:
return {"Info", {0.8f, 0.8f, 0.8f, 1.0f}};
}
}
void Push(Kind kind, std::string text) {
std::printf("Co-op: %s: %s\n", DisplayOf(kind).tag, text.c_str());
std::scoped_lock lock{history_mutex};
history.push_back({kind, std::move(text), {}});
++unread;
}
std::vector<HistoryEntry> GetHistory() {
std::scoped_lock lock{history_mutex};
return history;
}
void ClearHistory() {
std::scoped_lock lock{history_mutex};
history.clear();
unread = 0;
}
std::size_t UnreadCount() {
std::scoped_lock lock{history_mutex};
return unread;
}
void MarkRead() {
std::scoped_lock lock{history_mutex};
unread = 0;
}
void Register() {}
void Unregister() {}
} // namespace ImGui::ShadNetNotify
namespace ImGui::InvitationPrompt {
void Push(s32, std::string invitation_id, std::string, std::string from_npid) {
std::printf("Co-op: invitation %s from %s (invitations are not supported yet)\n",
invitation_id.c_str(), from_npid.c_str());
}
void Dismiss(const std::string&) {}
void Register() {}
void Unregister() {}
} // namespace ImGui::InvitationPrompt
// ---- Game identity -------------------------------------------------------------------------------
// ElfInfo lets shadPS4's Core::Emulator fill it in; this port has no emulator class, so the
// same name is used here for that one job.
namespace Core {
class Emulator {
public:
static void SetGame(const char* app0, const char* serial, const char* title,
const char* app_ver) {
auto& info = Common::ElfInfo::Instance();
info.game_serial = serial ? serial : "";
info.title = title ? title : "";
info.app_ver = app_ver ? app_ver : "";
info.game_folder = app0 ? app0 : "";
info.initialized = true;
info.npCommIds = ReadNpCommIds(info.game_folder / "sce_sys" / "npbind.dat");
}
private:
// npbind.dat: a 0x80-byte header, then tagged records (big-endian u16 tag and length);
// tag 0x10 holds an NP communication id such as NPWR05818_00.
static std::vector<std::string> ReadNpCommIds(const std::filesystem::path& path) {
std::vector<std::string> ids;
std::ifstream file{path, std::ios::binary};
const std::vector<u8> data{std::istreambuf_iterator<char>(file), {}};
for (size_t at = 0x80; at + 4 <= data.size();) {
const u16 tag = u16(data[at] << 8 | data[at + 1]);
const u16 size = u16(data[at + 2] << 8 | data[at + 3]);
at += 4;
if (at + size > data.size()) {
break;
}
if (tag == 0x10 && size) {
std::string id(reinterpret_cast<const char*>(&data[at]), size);
id.resize(std::strlen(id.c_str()));
ids.push_back(id);
}
at += size;
}
return ids;
}
};
} // namespace Core
extern "C" void bbnet_configure(const char* app0, const char* serial, const char* title,
const char* app_ver) {
Core::Emulator::SetGame(app0, serial, title, app_ver);
const auto& ids = Common::ElfInfo::Instance().GetNpCommIds();
std::printf("Co-op: %s, NP communication id %s%s\n", serial ? serial : "?",
ids.empty() ? "missing" : ids.front().c_str(),
EmulatorSettings.IsShadNetEnabled() ? "; online play on" : "");
}
// ---- Registration ------------------------------------------------------------------------------
extern "C" void bbnet_register(void) {
Core::Loader::SymbolsResolver resolver;
Core::Loader::SymbolsResolver* sym = &resolver;
Libraries::Net::RegisterLib(sym);
Libraries::NetCtl::RegisterLib(sym);
Libraries::Http::RegisterLib(sym);
Libraries::Http2::RegisterLib(sym);
Libraries::Ssl::RegisterLib(sym);
Libraries::Ssl2::RegisterLib(sym);
Libraries::Np::NpAuth::RegisterLib(sym);
Libraries::Np::NpCommon::RegisterLib(sym);
Libraries::Np::NpManager::RegisterLib(sym);
Libraries::Np::NpMatching2::RegisterLib(sym);
Libraries::Np::NpPartner::RegisterLib(sym);
Libraries::Np::NpParty::RegisterLib(sym);
Libraries::Np::NpScore::RegisterLib(sym);
Libraries::Np::NpSignaling::RegisterLib(sym);
Libraries::Np::NpTus::RegisterLib(sym);
Libraries::Np::NpWebApi::RegisterLib(sym);
Libraries::Np::NpWebApi2::RegisterLib(sym);
}
File diff suppressed because it is too large. Load diff
+332
View File
@@ -0,0 +1,332 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "common/types.h"
#include "core/libraries/network/ssl.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Http {
enum OrbisHttpNBEvents : u32 {
ORBIS_HTTP_NB_EVENT_IN = 0x00000001U, // Ready to receive (response data available).
ORBIS_HTTP_NB_EVENT_OUT = 0x00000002U, // Ready to send.
ORBIS_HTTP_NB_EVENT_SOCK_ERR = 0x00000008U, // Socket-level error during send/recv.
ORBIS_HTTP_NB_EVENT_HUP = 0x00000010U, // Request interrupted by the application.
ORBIS_HTTP_NB_EVENT_RESOLVED = 0x00010000U, // DNS resolution completed.
ORBIS_HTTP_NB_EVENT_RESOLVER_ERR = 0x00020000U, // DNS resolution failed.
};
enum OrbisHttpsFlags : u32 {
ORBIS_HTTPS_FLAG_SERVER_VERIFY = 0x01,
ORBIS_HTTPS_FLAG_CLIENT_VERIFY = 0x02,
ORBIS_HTTPS_FLAG_CN_CHECK = 0x04,
ORBIS_HTTPS_FLAG_NOT_AFTER_CHECK = 0x08,
ORBIS_HTTPS_FLAG_NOT_BEFORE_CHECK = 0x10,
ORBIS_HTTPS_FLAG_KNOWN_CA_CHECK = 0x20,
ORBIS_HTTPS_FLAG_SESSION_REUSE = 0x40,
ORBIS_HTTPS_FLAG_SNI = 0x80,
};
// Used as the initial value of HttpSettings::ssl_flags.
constexpr u32 ORBIS_HTTPS_FLAG_SDK_DEFAULT = ORBIS_HTTPS_FLAG_SERVER_VERIFY |
ORBIS_HTTPS_FLAG_CN_CHECK |
ORBIS_HTTPS_FLAG_KNOWN_CA_CHECK | ORBIS_HTTPS_FLAG_SNI;
// Validation masks consumed by sceHttpsEnableOption / sceHttpsDisableOption
constexpr u32 ORBIS_HTTPS_FLAG_PUBLIC_VALID = 0x000020ff;
constexpr u32 ORBIS_HTTPS_FLAG_PRIVATE_VALID = 0x00002dff;
enum OrbisHttpMethod : s32 {
ORBIS_HTTP_METHOD_GET = 0,
ORBIS_HTTP_METHOD_POST = 1,
ORBIS_HTTP_METHOD_HEAD = 2,
ORBIS_HTTP_METHOD_OPTIONS = 3,
ORBIS_HTTP_METHOD_PUT = 4,
ORBIS_HTTP_METHOD_DELETE = 5,
ORBIS_HTTP_METHOD_TRACE = 6,
ORBIS_HTTP_METHOD_CONNECT = 7,
ORBIS_HTTP_METHOD_CUSTOM = 8,
};
// mode argument for sceHttpAddRequestHeader.
enum OrbisHttpHeaderModes : s32 {
ORBIS_HTTP_HEADER_OVERWRITE = 0,
ORBIS_HTTP_HEADER_ADD = 1,
};
enum OrbisUriBuild : s32 {
ORBIS_HTTP_URI_BUILD_WITH_SCHEME = 0x01,
ORBIS_HTTP_URI_BUILD_WITH_HOSTNAME = 0x02,
ORBIS_HTTP_URI_BUILD_WITH_PORT = 0x04,
ORBIS_HTTP_URI_BUILD_WITH_PATH = 0x08,
ORBIS_HTTP_URI_BUILD_WITH_USERNAME = 0x10,
ORBIS_HTTP_URI_BUILD_WITH_PASSWORD = 0x20,
ORBIS_HTTP_URI_BUILD_WITH_QUERY = 0x40,
ORBIS_HTTP_URI_BUILD_WITH_FRAGMENT = 0x80
};
struct OrbisHttpUriElement {
bool opaque;
char* scheme;
char* username;
char* password;
char* hostname;
char* path;
char* query;
char* fragment;
u16 port;
u8 reserved[10];
};
struct HttpRequestInternal {
int state; // +0x20
int errorCode; // +0x28
int httpStatusCode; // +0x20C
std::mutex m_mutex;
};
// Memory pool statistics
struct OrbisHttpMemoryPoolStats {
u64 poolSize;
u64 maxInuseSize;
u64 currentInuseSize;
s32 reserved;
};
// Cookie statistics
struct OrbisHttpCookieStats {
u64 currentInuseSize;
u32 currentInuseNum;
u64 maxInuseSize;
u32 maxInuseNum;
u32 removedNum;
s32 reserved;
};
using OrbisHttpEpollHandle = void*;
// Non-blocking event reported by sceHttpWaitRequest.
struct OrbisHttpNBEvent {
u32 events;
u32 eventDetail;
int id;
void* userArg;
};
// Callbacks
using OrbisHttpAuthInfoCallback = int(PS4_SYSV_ABI*)(int request, int authType, const char* realm,
char* username, char* password,
int isNeedEntity, u8** entityBody,
u64* entitySize, int* isSave, void* userArg);
using OrbisHttpRedirectCallback = int(PS4_SYSV_ABI*)(int request, s32 statusCode, s32* method,
const char* location, void* userArg);
using OrbisHttpRequestStatusCallback = void(PS4_SYSV_ABI*)(int request, int requestStatus,
void* userArg);
using OrbisHttpCookieRecvCallback = int(PS4_SYSV_ABI*)(int request, const char* url,
const char* cookieHeader, u64 headerLen,
void* userArg);
using OrbisHttpCookieSendCallback = int(PS4_SYSV_ABI*)(int request, const char* url,
const char* cookieHeader, void* userArg);
using OrbisHttpsCallback = int(PS4_SYSV_ABI*)(int libsslCtxId, u32 verifyErr, void* const sslCert[],
int certNum, void* userArg);
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
// Functions
int PS4_SYSV_ABI sceHttpAddCookie(int libhttpCtxId, const char* url, const char* cookie,
u64 cookieLength);
int PS4_SYSV_ABI sceHttpAddQuery();
int PS4_SYSV_ABI sceHttpAddRequestHeaderRaw();
int PS4_SYSV_ABI sceHttpAuthCacheExport();
int PS4_SYSV_ABI sceHttpAuthCacheFlush(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpAuthCacheImport();
int PS4_SYSV_ABI sceHttpCacheRedirectedConnectionEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpCookieExport(int libhttpCtxId, void* buffer, u64 bufferSize,
u64* exportSize);
int PS4_SYSV_ABI sceHttpCookieFlush(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpCookieImport(int libhttpCtxId, const void* buffer, u64 bufferSize);
int PS4_SYSV_ABI sceHttpCreateConnection(int tmplId, const char* serverName, const char* scheme,
u16 port, int isEnableKeepalive);
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive);
int PS4_SYSV_ABI sceHttpCreateRequest(int connId, int method, const char* path, u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer,
int isAutoProxyConf);
int PS4_SYSV_ABI sceHttpDbgEnableProfile();
int PS4_SYSV_ABI sceHttpDbgGetConnectionStat();
int PS4_SYSV_ABI sceHttpDbgGetRequestStat();
int PS4_SYSV_ABI sceHttpDbgSetPrintf();
int PS4_SYSV_ABI sceHttpDbgShowConnectionStat();
int PS4_SYSV_ABI sceHttpDbgShowMemoryPoolStat();
int PS4_SYSV_ABI sceHttpDbgShowRequestStat();
int PS4_SYSV_ABI sceHttpDbgShowStat();
int PS4_SYSV_ABI sceHttpGetAuthEnabled(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpGetConnectionStat();
int PS4_SYSV_ABI sceHttpGetCookie(int libhttpCtxId, const char* url, char* cookie, u64* required,
u64 prepared, int isSecure);
int PS4_SYSV_ABI sceHttpGetCookieEnabled(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpGetCookieStats(int libhttpCtxId, OrbisHttpCookieStats* stats);
int PS4_SYSV_ABI sceHttpGetEpollId();
int PS4_SYSV_ABI sceHttpGetMemoryPoolStats(int libhttpCtxId, OrbisHttpMemoryPoolStats* currentStat);
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
int PS4_SYSV_ABI sceHttpRedirectCacheFlush(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpRequestGetAllHeaders();
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size);
int PS4_SYSV_ABI sceHttpSetAuthEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetAuthInfoCallback(int id, OrbisHttpAuthInfoCallback cbfunc,
void* userArg);
int PS4_SYSV_ABI sceHttpSetChunkedTransferEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetCookieEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetCookieMaxNum(int libhttpCtxId, u32 num);
int PS4_SYSV_ABI sceHttpSetCookieMaxNumPerDomain(int libhttpCtxId, u32 num);
int PS4_SYSV_ABI sceHttpSetCookieMaxSize(int libhttpCtxId, u32 size);
int PS4_SYSV_ABI sceHttpSetCookieRecvCallback(int id, OrbisHttpCookieRecvCallback cbfunc,
void* userArg);
int PS4_SYSV_ABI sceHttpSetCookieSendCallback(int id, OrbisHttpCookieSendCallback cbfunc,
void* userArg);
int PS4_SYSV_ABI sceHttpSetCookieTotalMaxSize(int libhttpCtxId, u32 size);
int PS4_SYSV_ABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int libhttpCtxId, int isEnable);
int PS4_SYSV_ABI sceHttpSetDelayBuildRequestEnabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetEpoll(int id, OrbisHttpEpollHandle eh, void* userArg);
int PS4_SYSV_ABI sceHttpSetEpollId();
int PS4_SYSV_ABI sceHttpSetHttp09Enabled(int id, int isEnable);
int PS4_SYSV_ABI sceHttpSetPolicyOption();
int PS4_SYSV_ABI sceHttpSetPriorityOption();
int PS4_SYSV_ABI sceHttpSetRedirectCallback(int id, OrbisHttpRedirectCallback cbfunc,
void* userArg);
int PS4_SYSV_ABI sceHttpSetRequestStatusCallback(int id, OrbisHttpRequestStatusCallback cbfunc,
void* userArg);
int PS4_SYSV_ABI sceHttpSetSocketCreationCallback();
int PS4_SYSV_ABI sceHttpsFreeCaList(int libhttpCtxId, OrbisHttpsCaList* caList);
int PS4_SYSV_ABI sceHttpsGetCaList(int httpCtxId, OrbisHttpsCaList* list);
int PS4_SYSV_ABI sceHttpsGetSslError(int id, int* errNum, u32* detail);
int PS4_SYSV_ABI sceHttpsLoadCert(int libhttpCtxId, int caCertNum, const void** caList,
const void* cert, const void* privKey);
int PS4_SYSV_ABI sceHttpsSetMinSslVersion(int id, int version);
int PS4_SYSV_ABI sceHttpsSetSslCallback(int id, OrbisHttpsCallback cbfunc, void* userArg);
int PS4_SYSV_ABI sceHttpsSetSslVersion(int id, int version);
int PS4_SYSV_ABI sceHttpsUnloadCert(int libhttpCtxId);
int PS4_SYSV_ABI sceHttpWaitRequest(OrbisHttpEpollHandle eh, OrbisHttpNBEvent* nbev, int maxevents,
int timeout);
int PS4_SYSV_ABI sceHttpUriCopy();
//***********************************
// Init/Terminate functions
//***********************************
int PS4_SYSV_ABI sceHttpTerm(int libhttpCtxId);
//***********************************
// Misc functions
//***********************************
int PS4_SYSV_ABI sceHttpSetRecvBlockSize(int id, u32 blockSize);
int PS4_SYSV_ABI sceHttpSetProxy(int id, int httpProxyConf, int wlanProxyConf, const char* host,
u16 port);
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpSetDefaultAcceptEncodingGZIPEnabled(int libhttpCtxId, int isEnable);
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled(int id, int isEnable);
//***********************************
// Non-blocking processing functions
//***********************************
int PS4_SYSV_ABI sceHttpCreateEpoll(int libhttpCtxId, OrbisHttpEpollHandle* eh);
int PS4_SYSV_ABI sceHttpDestroyEpoll(int libhttpCtxId, OrbisHttpEpollHandle eh);
int PS4_SYSV_ABI sceHttpGetEpoll(int id, OrbisHttpEpollHandle* eh, void** userArg);
int PS4_SYSV_ABI sceHttpUnsetEpoll(int id);
int PS4_SYSV_ABI sceHttpAbortWaitRequest(OrbisHttpEpollHandle eh);
int PS4_SYSV_ABI sceHttpGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpSetNonblock(int id, int isEnable);
int PS4_SYSV_ABI sceHttpTryGetNonblock(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpTrySetNonblock(int id, int isEnable);
//***********************************
// Http Communication functions
//***********************************
int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
int PS4_SYSV_ABI sceHttpAbortRequest(int reqId);
int PS4_SYSV_ABI sceHttpAbortRequestForce(int reqId);
//***********************************
// Https Option setting functions
//***********************************
int PS4_SYSV_ABI sceHttpsDisableOption(int id, u32 sslFlags);
int PS4_SYSV_ABI sceHttpsDisableOptionPrivate(int id, u32 sslFlags);
int PS4_SYSV_ABI sceHttpsEnableOption(int id, u32 sslFlags);
int PS4_SYSV_ABI sceHttpsEnableOptionPrivate(int id, u32 sslFlags);
//***********************************
// Response Information functions
//***********************************
int PS4_SYSV_ABI sceHttpSetResolveTimeOut(int id, u32 usec);
int PS4_SYSV_ABI sceHttpSetResponseHeaderMaxSize(int id, u64 headerSize);
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
int PS4_SYSV_ABI sceHttpGetResponseContentLength(int reqId, int* result, u64* contentLength);
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
int PS4_SYSV_ABI sceHttpSetInflateGZIPEnabled(int id, int isEnable);
//***********************************
// Http Header setting functions
//***********************************
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode);
int PS4_SYSV_ABI sceHttpRemoveRequestHeader(int id, const char* name);
int PS4_SYSV_ABI sceHttpSetRequestContentLength(int id, u64 contentLength);
//***********************************
// Redirection setting functions
//***********************************
int PS4_SYSV_ABI sceHttpGetAutoRedirect(int id, int* isEnable);
int PS4_SYSV_ABI sceHttpSetAutoRedirect(int id, int isEnable);
//***********************************
// Timeout settting functions
//***********************************
int PS4_SYSV_ABI sceHttpSetResolveRetry(int id, int retry);
int PS4_SYSV_ABI sceHttpSetConnectTimeOut(int id, u32 usec);
int PS4_SYSV_ABI sceHttpSetSendTimeOut(int id, u32 usec);
int PS4_SYSV_ABI sceHttpSetRecvTimeOut(int id, u32 usec);
//***********************************
// Connection functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteConnection(int connId);
//***********************************
// Template functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteTemplate(int tmplId);
//***********************************
// Request functions
//***********************************
int PS4_SYSV_ABI sceHttpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceHttpCreateRequest2(int connId, const char* method, const char* path,
u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2(int connId, const char* method, const char* url,
u64 contentLength);
//***********************************
// Error Obtainment functions
//***********************************
int PS4_SYSV_ABI sceHttpGetLastErrno(int reqId, int* errNum);
//***********************************
// HTTP Header Parsing functions
//***********************************
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
int32_t* httpMinorVer, int32_t* responseCode,
const char** reasonPhrase, u64* phraseLen);
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
const char** fieldValue, u64* valueLen);
//***********************************
// URI functions
//***********************************
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
const OrbisHttpUriElement* srcElement, u32 option);
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in);
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
u64 prepare, u32 option);
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
u64* require, u64 prepare);
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize);
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Http
@@ -0,0 +1,417 @@
// SPDX-FileCopyrightText: Copyright 2024 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/network/http.h"
#include "core/libraries/network/http2.h"
#include <map>
namespace Libraries::Http2 {
std::map<s32, s32> requests_to_connections{};
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpAbortRequest(req_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get abort HTTP request, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2AddCookie() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpAddRequestHeader(template_or_req_id, name, value, mode);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to add HTTP response header, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2AuthCacheFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CookieExport() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CookieFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CookieImport() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CreateCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
// Http1 has a connection thing to go through first
s32 conn_id = Libraries::Http::sceHttpCreateConnectionWithURL(tmpl_id, url, true);
if (conn_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP connection, error = {:#x}", conn_id);
return conn_id;
}
s32 req_id =
Libraries::Http::sceHttpCreateRequestWithURL2(conn_id, method, url, content_length);
if (req_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP request, error = {:#x}", req_id);
return req_id;
}
requests_to_connections[req_id] = conn_id;
return req_id;
}
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
s32 auto_proxy_conf) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 tmpl_id =
Libraries::Http::sceHttpCreateTemplate(ctx_id, user_agent, http_ver, auto_proxy_conf);
if (tmpl_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to create HTTP template, error = {:#x}", tmpl_id);
}
return tmpl_id;
}
s32 PS4_SYSV_ABI sceHttp2DeleteCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpDeleteRequest(req_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP request, error = {:#x}", result);
return result;
}
s32 conn_id = requests_to_connections[req_id];
result = Libraries::Http::sceHttpDeleteConnection(conn_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP connection, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpDeleteTemplate(tmpl_id);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to delete HTTP template, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpGetAllResponseHeaders(req_id, header, header_size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get HTTP response headers, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2GetAuthEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetAutoRedirect() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetCookie() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetCookieStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetMemoryPoolStats() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetResponseContentLength() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 req_id, s32* status_code) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpGetStatusCode(req_id, status_code);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to get HTTP status code, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests) {
LOG_ERROR(Lib_Http2, "(DUMMY) called");
s32 ctx_id = Libraries::Http::sceHttpInit(net_id, ssl_id, pool_size);
if (ctx_id < 0) {
LOG_ERROR(Lib_Http2, "Failed to init HTTP context, error = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpReadData(req_id, data, size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to read HTTP data, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2ReadDataAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2RedirectCacheFlush() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2RemoveRequestHeader() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpSendRequest(req_id, data, size);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to send HTTP request, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2SendRequestAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetAuthEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetAuthInfoCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetAutoRedirect() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetConnectionWaitTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetConnectTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieBox() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNum() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxNumPerDomain() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieMaxSize() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieRecvCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetCookieSendCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetInflateGZIPEnabled() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetMinSslVersion() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
void* user_arg) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetRecvTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetRedirectCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length) {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
s32 result = Libraries::Http::sceHttpSetRequestContentLength(req_id, content_length);
if (result < 0) {
LOG_ERROR(Lib_Http2, "Failed to set HTTP request content length, error = {:#x}", result);
}
return result;
}
s32 PS4_SYSV_ABI sceHttp2SetResolveRetry() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetResolveTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetSendTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetSslCallback() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SetTimeOut() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SslDisableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2SslEnableOption() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2Term() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceHttp2WaitAsync() {
LOG_ERROR(Lib_Http2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("IZ-qjhRqvjk", "libSceHttp2", 1, "libSceHttp2", sceHttp2AbortRequest);
LIB_FUNCTION("flPxnowtvWY", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddCookie);
LIB_FUNCTION("nrPfOE8TQu0", "libSceHttp2", 1, "libSceHttp2", sceHttp2AddRequestHeader);
LIB_FUNCTION("WeuDjj5m4YU", "libSceHttp2", 1, "libSceHttp2", sceHttp2AuthCacheFlush);
LIB_FUNCTION("JlFGR4v50Kw", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieExport);
LIB_FUNCTION("5VlQSzXW-SQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieFlush);
LIB_FUNCTION("B5ibZI5UlzU", "libSceHttp2", 1, "libSceHttp2", sceHttp2CookieImport);
LIB_FUNCTION("N4UfjvWJsMw", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateCookieBox);
LIB_FUNCTION("mmyOCxQMVYQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateRequestWithURL);
LIB_FUNCTION("+wCt7fCijgk", "libSceHttp2", 1, "libSceHttp2", sceHttp2CreateTemplate);
LIB_FUNCTION("O9ync3F-JVI", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteCookieBox);
LIB_FUNCTION("c8D9qIjo8EY", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteRequest);
LIB_FUNCTION("pDom5-078DA", "libSceHttp2", 1, "libSceHttp2", sceHttp2DeleteTemplate);
LIB_FUNCTION("-rdXUi2XW90", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAllResponseHeaders);
LIB_FUNCTION("m-OL13q8AI8", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAuthEnabled);
LIB_FUNCTION("od5QCZhZSfw", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetAutoRedirect);
LIB_FUNCTION("GQFGj0rYX+A", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookie);
LIB_FUNCTION("IX23slKvtQI", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookieBox);
LIB_FUNCTION("eij7UzkUqK8", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetCookieStats);
LIB_FUNCTION("otUQuZa-mv0", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetMemoryPoolStats);
LIB_FUNCTION("o0DBQpFE13o", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetResponseContentLength);
LIB_FUNCTION("9XYJwCf3lEA", "libSceHttp2", 1, "libSceHttp2", sceHttp2GetStatusCode);
LIB_FUNCTION("3JCe3lCbQ8A", "libSceHttp2", 1, "libSceHttp2", sceHttp2Init);
LIB_FUNCTION("QygCNNmbGss", "libSceHttp2", 1, "libSceHttp2", sceHttp2ReadData);
LIB_FUNCTION("bGN-6zbo7ms", "libSceHttp2", 1, "libSceHttp2", sceHttp2ReadDataAsync);
LIB_FUNCTION("klwUy2Wg+q8", "libSceHttp2", 1, "libSceHttp2", sceHttp2RedirectCacheFlush);
LIB_FUNCTION("jHdP0CS4ZlA", "libSceHttp2", 1, "libSceHttp2", sceHttp2RemoveRequestHeader);
LIB_FUNCTION("rbqZig38AT8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SendRequest);
LIB_FUNCTION("A+NVAFu4eCg", "libSceHttp2", 1, "libSceHttp2", sceHttp2SendRequestAsync);
LIB_FUNCTION("jjFahkBPCYs", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAuthEnabled);
LIB_FUNCTION("Wwj6HbB2mOo", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAuthInfoCallback);
LIB_FUNCTION("b9AvoIaOuHI", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetAutoRedirect);
LIB_FUNCTION("n8hMLe31OPA", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetConnectionWaitTimeOut);
LIB_FUNCTION("-HIO4VT87v8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetConnectTimeOut);
LIB_FUNCTION("jrVHsKCXA0g", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieBox);
LIB_FUNCTION("mPKVhQqh2Es", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxNum);
LIB_FUNCTION("o7+WXe4WadE", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxNumPerDomain);
LIB_FUNCTION("6a0N6GPD7RM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieMaxSize);
LIB_FUNCTION("zdtXKn9X7no", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieRecvCallback);
LIB_FUNCTION("McYmUpQ3-DY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetCookieSendCallback);
LIB_FUNCTION("uRosf8GQbHQ", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetInflateGZIPEnabled);
LIB_FUNCTION("09tk+kIA1Ns", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetMinSslVersion);
LIB_FUNCTION("UL4Fviw+IAM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetPreSendCallback);
LIB_FUNCTION("izvHhqgDt44", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRecvTimeOut);
LIB_FUNCTION("BJgi0CH7al4", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRedirectCallback);
LIB_FUNCTION("FSAFOzi0FpM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetRequestContentLength);
LIB_FUNCTION("Gcjh+CisAZM", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetResolveRetry);
LIB_FUNCTION("ACjtE27aErY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetResolveTimeOut);
LIB_FUNCTION("XPtW45xiLHk", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetSendTimeOut);
LIB_FUNCTION("YrWX+DhPHQY", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetSslCallback);
LIB_FUNCTION("VYMxTcBqSE0", "libSceHttp2", 1, "libSceHttp2", sceHttp2SetTimeOut);
LIB_FUNCTION("B37SruheQ5Y", "libSceHttp2", 1, "libSceHttp2", sceHttp2SslDisableOption);
LIB_FUNCTION("EWcwMpbr5F8", "libSceHttp2", 1, "libSceHttp2", sceHttp2SslEnableOption);
LIB_FUNCTION("YiBUtz-pGkc", "libSceHttp2", 1, "libSceHttp2", sceHttp2Term);
LIB_FUNCTION("MOp-AUhdfi8", "libSceHttp2", 1, "libSceHttp2", sceHttp2WaitAsync);
};
} // namespace Libraries::Http2
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/ssl2.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Http2 {
enum OrbisHttp2HttpVersion : u32 {
ORBIS_HTTP2_VERSION_1_0 = 1,
ORBIS_HTTP2_VERSION_1_1 = 2,
ORBIS_HTTP2_VERSION_2_0 = 3,
};
// TODO: Figure out the whole struct. What's here is based on libSceNpWebApi2 use.
struct OrbisHttp2PreSendCallbackData {
void* unk;
void* cert;
};
using OrbisHttp2PreSendCallback = PS4_SYSV_ABI s32 (*)(s32 request_id, s32 ssl_id,
OrbisHttp2PreSendCallbackData* data,
void* user_arg);
s32 PS4_SYSV_ABI sceHttp2AbortRequest(s32 req_id);
s32 PS4_SYSV_ABI sceHttp2AddRequestHeader(s32 template_or_req_id, const char* name,
const char* value, u32 mode);
s32 PS4_SYSV_ABI sceHttp2CreateRequestWithURL(s32 tmpl_id, const char* method, const char* url,
u64 content_length);
s32 PS4_SYSV_ABI sceHttp2CreateTemplate(s32 ctx_id, const char* user_agent, s32 http_ver,
s32 auto_proxy_conf);
s32 PS4_SYSV_ABI sceHttp2DeleteRequest(s32 req_id);
s32 PS4_SYSV_ABI sceHttp2DeleteTemplate(s32 tmpl_id);
s32 PS4_SYSV_ABI sceHttp2GetAllResponseHeaders(s32 req_id, char** header, u64* header_size);
s32 PS4_SYSV_ABI sceHttp2GetStatusCode(s32 request_id, s32* status_code);
s32 PS4_SYSV_ABI sceHttp2Init(s32 net_id, s32 ssl_id, u64 pool_size, s32 max_requests);
s32 PS4_SYSV_ABI sceHttp2ReadData(s32 req_id, void* data, u64 size);
s32 PS4_SYSV_ABI sceHttp2SendRequest(s32 req_id, const void* data, u64 size);
s32 PS4_SYSV_ABI sceHttp2SetPreSendCallback(s32 template_id, OrbisHttp2PreSendCallback cb_func,
void* user_arg);
s32 PS4_SYSV_ABI sceHttp2SetRequestContentLength(s32 req_id, u64 content_length);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Http2
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
constexpr int ORBIS_HTTP_ERROR_BEFORE_INIT = 0x80431001;
constexpr int ORBIS_HTTP_ERROR_ALREADY_INITED = 0x80431020;
constexpr int ORBIS_HTTP_ERROR_BUSY = 0x80431021;
constexpr int ORBIS_HTTP_ERROR_OUT_OF_MEMORY = 0x80431022;
constexpr int ORBIS_HTTP_ERROR_NOT_FOUND = 0x80431025;
constexpr int ORBIS_HTTP_ERROR_INVALID_VERSION = 0x8043106a;
constexpr int ORBIS_HTTP_ERROR_INVALID_ID = 0x80431100;
constexpr int ORBIS_HTTP_ERROR_OUT_OF_SIZE = 0x80431104;
constexpr int ORBIS_HTTP_ERROR_INVALID_VALUE = 0x804311fe;
constexpr int ORBIS_HTTP_ERROR_INVALID_URL = 0x80433060;
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_SCHEME = 0x80431061;
constexpr int ORBIS_HTTP_ERROR_NETWORK = 0x80431063;
constexpr int ORBIS_HTTP_ERROR_BAD_RESPONSE = 0x80431064;
constexpr int ORBIS_HTTP_ERROR_BEFORE_SEND = 0x80431065;
constexpr int ORBIS_HTTP_ERROR_AFTER_SEND = 0x80431066;
constexpr int ORBIS_HTTP_ERROR_TIMEOUT = 0x80431068;
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_AUTH_TYPE = 0x80431069;
constexpr int ORBIS_HTTP_ERROR_UNKNOWN_METHOD = 0x8043106b;
constexpr int ORBIS_HTTP_ERROR_READ_BY_HEAD_METHOD = 0x8043106f;
constexpr int ORBIS_HTTP_ERROR_NOT_IN_COM = 0x80431070;
constexpr int ORBIS_HTTP_ERROR_NO_CONTENT_LENGTH = 0x80431071;
constexpr int ORBIS_HTTP_ERROR_CHUNK_ENC = 0x80431072;
constexpr int ORBIS_HTTP_ERROR_TOO_LARGE_RESPONSE_HEADER = 0x80431073;
constexpr int ORBIS_HTTP_ERROR_SSL = 0x80431075;
constexpr int ORBIS_HTTP_ERROR_INSUFFICIENT_STACKSIZE = 0x80431076;
constexpr int ORBIS_HTTP_ERROR_ABORTED = 0x80431080;
constexpr int ORBIS_HTTP_ERROR_UNKNOWN = 0x80431081;
constexpr int ORBIS_HTTP_ERROR_EAGAIN = 0x80431082;
constexpr int ORBIS_HTTP_ERROR_PROXY = 0x80431084;
constexpr int ORBIS_HTTP_ERROR_BROKEN = 0x80431085;
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_NOT_FOUND = 0x80432025;
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE = 0x80432060;
constexpr int ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE = 0x804321fe;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_EPACKET = 0x80436001;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENODNS = 0x80436002;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ETIMEDOUT = 0x80436003;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOSUPPORT = 0x80436004;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_EFORMAT = 0x80436005;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERFAILURE = 0x80436006;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOHOST = 0x80436007;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x80436008;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ESERVERREFUSED = 0x80436009;
constexpr int ORBIS_HTTP_ERROR_RESOLVER_ENORECORD = 0x8043600a;
constexpr int ORBIS_HTTPS_ERROR_CERT = 0x80435060;
constexpr int ORBIS_HTTPS_ERROR_HANDSHAKE = 0x80435061;
constexpr int ORBIS_HTTPS_ERROR_IO = 0x80435062;
constexpr int ORBIS_HTTPS_ERROR_INTERNAL = 0x80435063;
constexpr int ORBIS_HTTPS_ERROR_PROXY = 0x80435064;
constexpr int ORBIS_HTTPS_ERROR_SSL_INTERNAL = 0x01;
constexpr int ORBIS_HTTPS_ERROR_SSL_INVALID_CERT = 0x02;
constexpr int ORBIS_HTTPS_ERROR_SSL_CN_CHECK = 0x04;
constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_AFTER_CHECK = 0x08;
constexpr int ORBIS_HTTPS_ERROR_SSL_NOT_BEFORE_CHECK = 0x10;
constexpr int ORBIS_HTTPS_ERROR_SSL_UNKNOWN_CA = 0x20;
File diff suppressed because it is too large. Load diff
+495
View File
@@ -0,0 +1,495 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/types.h"
#include "netctl.h"
namespace Core::Loader {
class SymbolsResolver;
}
// Define our own htonll and ntohll because its not available in some systems/platforms
#ifndef HTONLL
#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))
#endif
#ifndef NTOHLL
#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))
#endif
namespace Libraries::Net {
static int ConvertFamilies(int family);
enum OrbisNetFamily : u32 {
ORBIS_NET_AF_UNIX = 1,
ORBIS_NET_AF_INET = 2,
ORBIS_NET_AF_INET6 = 28,
};
enum OrbisNetSocketType : u32 {
ORBIS_NET_SOCK_STREAM = 1,
ORBIS_NET_SOCK_DGRAM = 2,
ORBIS_NET_SOCK_RAW = 3,
ORBIS_NET_SOCK_DGRAM_P2P = 6,
ORBIS_NET_SOCK_STREAM_P2P = 10
};
enum OrbisNetProtocol : u32 {
ORBIS_NET_IPPROTO_IP = 0,
ORBIS_NET_IPPROTO_ICMP = 1,
ORBIS_NET_IPPROTO_IGMP = 2,
ORBIS_NET_IPPROTO_TCP = 6,
ORBIS_NET_IPPROTO_UDP = 17,
ORBIS_NET_IPPROTO_IPV6 = 41,
ORBIS_NET_SOL_SOCKET = 0xFFFF
};
constexpr std::string_view NameOf(OrbisNetProtocol p) {
switch (p) {
case ORBIS_NET_IPPROTO_IP:
return "ORBIS_NET_IPPROTO_IP";
case ORBIS_NET_IPPROTO_ICMP:
return "ORBIS_NET_IPPROTO_ICMP";
case ORBIS_NET_IPPROTO_IGMP:
return "ORBIS_NET_IPPROTO_IGMP";
case ORBIS_NET_IPPROTO_TCP:
return "ORBIS_NET_IPPROTO_TCP";
case ORBIS_NET_IPPROTO_UDP:
return "ORBIS_NET_IPPROTO_UDP";
case ORBIS_NET_IPPROTO_IPV6:
return "ORBIS_NET_IPPROTO_IPV6";
case ORBIS_NET_SOL_SOCKET:
return "ORBIS_NET_SOL_SOCKET";
default:
UNREACHABLE_MSG("{}", (u32)p);
}
}
enum OrbisNetSocketIpOption : u32 {
ORBIS_NET_IP_HDRINCL = 2,
ORBIS_NET_IP_TOS = 3,
ORBIS_NET_IP_TTL = 4,
ORBIS_NET_IP_MULTICAST_IF = 9,
ORBIS_NET_IP_MULTICAST_TTL = 10,
ORBIS_NET_IP_MULTICAST_LOOP = 11,
ORBIS_NET_IP_ADD_MEMBERSHIP = 12,
ORBIS_NET_IP_DROP_MEMBERSHIP = 13,
ORBIS_NET_IP_TTLCHK = 23,
ORBIS_NET_IP_MAXTTL = 24,
};
enum OrbisNetSocketTcpOption : u32 {
ORBIS_NET_TCP_NODELAY = 1,
ORBIS_NET_TCP_MAXSEG = 2,
ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3,
};
enum OrbisNetSocketSoOption : u32 {
ORBIS_NET_SO_REUSEADDR = 0x00000004,
ORBIS_NET_SO_KEEPALIVE = 0x00000008,
ORBIS_NET_SO_BROADCAST = 0x00000020,
ORBIS_NET_SO_LINGER = 0x00000080,
ORBIS_NET_SO_REUSEPORT = 0x00000200,
ORBIS_NET_SO_ONESBCAST = 0x00010000,
ORBIS_NET_SO_USECRYPTO = 0x00020000,
ORBIS_NET_SO_USESIGNATURE = 0x00040000,
ORBIS_NET_SO_SNDBUF = 0x1001,
ORBIS_NET_SO_RCVBUF = 0x1002,
ORBIS_NET_SO_ERROR = 0x1007,
ORBIS_NET_SO_TYPE = 0x1008,
ORBIS_NET_SO_SNDTIMEO = 0x1105,
ORBIS_NET_SO_RCVTIMEO = 0x1106,
ORBIS_NET_SO_ERROR_EX = 0x1107,
ORBIS_NET_SO_ACCEPTTIMEO = 0x1108,
ORBIS_NET_SO_CONNECTTIMEO = 0x1109,
ORBIS_NET_SO_NBIO = 0x1200,
ORBIS_NET_SO_POLICY = 0x1201,
ORBIS_NET_SO_NAME = 0x1202,
ORBIS_NET_SO_PRIORITY = 0x1203
};
enum OrbisNetFlags : u32 {
ORBIS_NET_MSG_PEEK = 0x00000002,
ORBIS_NET_MSG_WAITALL = 0x00000040,
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
};
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
switch (o) {
case ORBIS_NET_SO_REUSEADDR:
return "ORBIS_NET_SO_REUSEADDR";
case ORBIS_NET_SO_KEEPALIVE:
return "ORBIS_NET_SO_KEEPALIVE";
case ORBIS_NET_SO_BROADCAST:
return "ORBIS_NET_SO_BROADCAST";
case ORBIS_NET_SO_LINGER:
return "ORBIS_NET_SO_LINGER";
case ORBIS_NET_SO_REUSEPORT:
return "ORBIS_NET_SO_REUSEPORT";
case ORBIS_NET_SO_ONESBCAST:
return "ORBIS_NET_SO_ONESBCAST";
case ORBIS_NET_SO_USECRYPTO:
return "ORBIS_NET_SO_USECRYPTO";
case ORBIS_NET_SO_USESIGNATURE:
return "ORBIS_NET_SO_USESIGNATURE";
case ORBIS_NET_SO_SNDBUF:
return "ORBIS_NET_SO_SNDBUF";
case ORBIS_NET_SO_RCVBUF:
return "ORBIS_NET_SO_RCVBUF";
case ORBIS_NET_SO_ERROR:
return "ORBIS_NET_SO_ERROR";
case ORBIS_NET_SO_TYPE:
return "ORBIS_NET_SO_TYPE";
case ORBIS_NET_SO_SNDTIMEO:
return "ORBIS_NET_SO_SNDTIMEO";
case ORBIS_NET_SO_RCVTIMEO:
return "ORBIS_NET_SO_RCVTIMEO";
case ORBIS_NET_SO_ERROR_EX:
return "ORBIS_NET_SO_ERROR_EX";
case ORBIS_NET_SO_ACCEPTTIMEO:
return "ORBIS_NET_SO_ACCEPTTIMEO";
case ORBIS_NET_SO_CONNECTTIMEO:
return "ORBIS_NET_SO_CONNECTTIMEO";
case ORBIS_NET_SO_NBIO:
return "ORBIS_NET_SO_NBIO";
case ORBIS_NET_SO_POLICY:
return "ORBIS_NET_SO_POLICY";
case ORBIS_NET_SO_NAME:
return "ORBIS_NET_SO_NAME";
case ORBIS_NET_SO_PRIORITY:
return "ORBIS_NET_SO_PRIORITY";
default:
UNREACHABLE_MSG("{}", (u32)o);
}
}
enum OrbisNetEpollFlag : u32 {
ORBIS_NET_EPOLL_CTL_ADD = 1,
ORBIS_NET_EPOLL_CTL_MOD = 2,
ORBIS_NET_EPOLL_CTL_DEL = 3,
};
enum OrbisNetEpollEvents : u32 {
ORBIS_NET_EPOLLIN = 0x1,
ORBIS_NET_EPOLLOUT = 0x2,
ORBIS_NET_EPOLLERR = 0x8,
ORBIS_NET_EPOLLHUP = 0x10,
ORBIS_NET_EPOLLDESCID = 0x10000,
};
enum OrbisNetResolverFlag : u32 {
ORBIS_NET_RESOLVER_ASYNC = 0x1,
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000,
};
using OrbisNetId = s32;
struct OrbisNetSockaddr {
u8 sa_len;
u8 sa_family;
char sa_data[14];
};
struct OrbisNetSockaddrIn {
u8 sin_len;
u8 sin_family;
u16 sin_port;
u32 sin_addr;
u16 sin_vport;
char sin_zero[6];
};
using OrbisNetInAddr_t = u32;
struct OrbisNetInAddr {
OrbisNetInAddr_t inaddr_addr;
};
struct OrbisNetSockaddrUn {
u8 sun_len;
u8 sun_family; // AF_UNIX
char sun_path[104];
};
struct OrbisNetIovec {
void* iov_base;
u64 iov_len;
};
struct OrbisNetMsghdr {
void* msg_name;
u32 msg_namelen;
OrbisNetIovec* msg_iov;
int msg_iovlen;
void* msg_control;
u32 msg_controllen;
int msg_flags;
};
union OrbisNetEpollData {
void* ptr;
u32 data_u32;
int fd;
u64 data_u64;
};
struct OrbisNetEpollEvent {
u32 events;
u32 pad;
u64 ident;
OrbisNetEpollData data;
};
union OrbisNetAddrUnion {
OrbisNetInAddr addr;
u8 addr6[16];
};
struct OrbisNetResolverAddr {
OrbisNetAddrUnion u;
u32 af;
u32 pad[3];
};
struct OrbisNetResolverInfo {
OrbisNetResolverAddr addrs[10];
u32 records;
u32 recordsv4;
u32 pad[14];
};
int PS4_SYSV_ABI in6addr_any();
int PS4_SYSV_ABI in6addr_loopback();
int PS4_SYSV_ABI sce_net_dummy();
int PS4_SYSV_ABI sce_net_in6addr_any();
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes();
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters();
int PS4_SYSV_ABI sce_net_in6addr_loopback();
int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes();
OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetAddrConfig6GetInfo();
int PS4_SYSV_ABI sceNetAddrConfig6Start();
int PS4_SYSV_ABI sceNetAddrConfig6Stop();
int PS4_SYSV_ABI sceNetAllocateAllRouteInfo();
int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic();
int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam();
int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam();
int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy();
int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam();
int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam();
int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy();
int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetClearDnsCache();
int PS4_SYSV_ABI sceNetConfigAddArp();
int PS4_SYSV_ABI sceNetConfigAddArpWithInterface();
int PS4_SYSV_ABI sceNetConfigAddIfaddr();
int PS4_SYSV_ABI sceNetConfigAddMRoute();
int PS4_SYSV_ABI sceNetConfigAddRoute();
int PS4_SYSV_ABI sceNetConfigAddRoute6();
int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface();
int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces();
int PS4_SYSV_ABI sceNetConfigDelArp();
int PS4_SYSV_ABI sceNetConfigDelArpWithInterface();
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute();
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigDelIfaddr();
int PS4_SYSV_ABI sceNetConfigDelIfaddr6();
int PS4_SYSV_ABI sceNetConfigDelMRoute();
int PS4_SYSV_ABI sceNetConfigDelRoute();
int PS4_SYSV_ABI sceNetConfigDelRoute6();
int PS4_SYSV_ABI sceNetConfigDownInterface();
int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode();
int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent();
int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode();
int PS4_SYSV_ABI sceNetConfigFlushRoute();
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute();
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigGetIfaddr();
int PS4_SYSV_ABI sceNetConfigGetIfaddr6();
int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig();
int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar();
int PS4_SYSV_ABI sceNetConfigRoutingStart();
int PS4_SYSV_ABI sceNetConfigRoutingStop();
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute();
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigSetDefaultScope();
int PS4_SYSV_ABI sceNetConfigSetIfaddr();
int PS4_SYSV_ABI sceNetConfigSetIfaddr6();
int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags();
int PS4_SYSV_ABI sceNetConfigSetIfFlags();
int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6();
int PS4_SYSV_ABI sceNetConfigSetIfmtu();
int PS4_SYSV_ABI sceNetConfigUnsetIfFlags();
int PS4_SYSV_ABI sceNetConfigUpInterface();
int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags();
int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate();
int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo();
int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin();
int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin();
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement();
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanApStart();
int PS4_SYSV_ABI sceNetConfigWlanApStop();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop();
int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo();
int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna();
int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate();
int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig();
int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo();
int PS4_SYSV_ABI sceNetConfigWlanInfraLeave();
int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin();
int PS4_SYSV_ABI sceNetConfigWlanScan();
int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig();
int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetControl();
int PS4_SYSV_ABI sceNetDhcpdStart();
int PS4_SYSV_ABI sceNetDhcpdStop();
int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo();
int PS4_SYSV_ABI sceNetDhcpGetInfo();
int PS4_SYSV_ABI sceNetDhcpGetInfoEx();
int PS4_SYSV_ABI sceNetDhcpStart();
int PS4_SYSV_ABI sceNetDhcpStop();
int PS4_SYSV_ABI sceNetDumpAbort();
int PS4_SYSV_ABI sceNetDumpCreate();
int PS4_SYSV_ABI sceNetDumpDestroy();
int PS4_SYSV_ABI sceNetDumpRead();
int PS4_SYSV_ABI sceNetDuplicateIpStart();
int PS4_SYSV_ABI sceNetDuplicateIpStop();
int PS4_SYSV_ABI sceNetEpollAbort();
int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id,
OrbisNetEpollEvent* event);
int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags);
int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid);
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
int timeout);
int* PS4_SYSV_ABI sceNetErrnoLoc();
int PS4_SYSV_ABI sceNetEtherNtostr();
int PS4_SYSV_ABI sceNetEtherStrton();
int PS4_SYSV_ABI sceNetEventCallbackCreate();
int PS4_SYSV_ABI sceNetEventCallbackDestroy();
int PS4_SYSV_ABI sceNetEventCallbackGetError();
int PS4_SYSV_ABI sceNetEventCallbackWaitCB();
int PS4_SYSV_ABI sceNetFreeAllRouteInfo();
int PS4_SYSV_ABI sceNetGetArpInfo();
int PS4_SYSV_ABI sceNetGetDns6Info();
int PS4_SYSV_ABI sceNetGetDnsInfo();
int PS4_SYSV_ABI sceNetGetIfList();
int PS4_SYSV_ABI sceNetGetIfListOnce();
int PS4_SYSV_ABI sceNetGetIfName();
int PS4_SYSV_ABI sceNetGetIfnameNumList();
int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags);
int PS4_SYSV_ABI sceNetGetMemoryPoolStats();
int PS4_SYSV_ABI sceNetGetNameToIndex();
int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetGetRandom();
int PS4_SYSV_ABI sceNetGetRouteInfo();
int PS4_SYSV_ABI sceNetGetSockInfo();
int PS4_SYSV_ABI sceNetGetSockInfo6();
int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
int PS4_SYSV_ABI sceNetGetStatisticsInfo();
int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal();
int PS4_SYSV_ABI sceNetGetSystemTime();
u32 PS4_SYSV_ABI sceNetHtonl(u32 host32);
u64 PS4_SYSV_ABI sceNetHtonll(u64 host64);
u16 PS4_SYSV_ABI sceNetHtons(u16 host16);
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size);
int PS4_SYSV_ABI sceNetInetNtopWithScopeId();
int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst);
int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags);
int PS4_SYSV_ABI sceNetInetPtonWithScopeId();
int PS4_SYSV_ABI sceNetInfoDumpStart();
int PS4_SYSV_ABI sceNetInfoDumpStop();
int PS4_SYSV_ABI sceNetInit();
int PS4_SYSV_ABI sceNetInitParam();
int PS4_SYSV_ABI sceNetIoctl();
int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog);
int PS4_SYSV_ABI sceNetMemoryAllocate();
int PS4_SYSV_ABI sceNetMemoryFree();
u32 PS4_SYSV_ABI sceNetNtohl(u32 net32);
int PS4_SYSV_ABI sceNetNtohll();
u16 PS4_SYSV_ABI sceNetNtohs(u16 net16);
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags);
int PS4_SYSV_ABI sceNetPoolDestroy();
int PS4_SYSV_ABI sceNetPppoeStart();
int PS4_SYSV_ABI sceNetPppoeStop();
int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags);
int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen);
int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
int PS4_SYSV_ABI sceNetResolverAbort();
int PS4_SYSV_ABI sceNetResolverConnect();
int PS4_SYSV_ABI sceNetResolverConnectAbort();
int PS4_SYSV_ABI sceNetResolverConnectCreate();
int PS4_SYSV_ABI sceNetResolverConnectDestroy();
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags);
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid);
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status);
int PS4_SYSV_ABI sceNetResolverStartAton();
int PS4_SYSV_ABI sceNetResolverStartAton6();
int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname,
OrbisNetInAddr* addr, int timeout, int retry, int flags);
int PS4_SYSV_ABI sceNetResolverStartNtoa6();
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname,
OrbisNetResolverInfo* info, int timeout,
int retry, int flags);
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx();
int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags);
int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetSetDns6Info();
int PS4_SYSV_ABI sceNetSetDns6InfoToKernel();
int PS4_SYSV_ABI sceNetSetDnsInfo();
int PS4_SYSV_ABI sceNetSetDnsInfoToKernel();
int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen);
int PS4_SYSV_ABI sceNetShowIfconfig();
int PS4_SYSV_ABI sceNetShowIfconfigForBuffer();
int PS4_SYSV_ABI sceNetShowIfconfigWithMemory();
int PS4_SYSV_ABI sceNetShowNetstat();
int PS4_SYSV_ABI sceNetShowNetstatEx();
int PS4_SYSV_ABI sceNetShowNetstatExForBuffer();
int PS4_SYSV_ABI sceNetShowNetstatForBuffer();
int PS4_SYSV_ABI sceNetShowNetstatWithMemory();
int PS4_SYSV_ABI sceNetShowPolicy();
int PS4_SYSV_ABI sceNetShowPolicyWithMemory();
int PS4_SYSV_ABI sceNetShowRoute();
int PS4_SYSV_ABI sceNetShowRoute6();
int PS4_SYSV_ABI sceNetShowRoute6ForBuffer();
int PS4_SYSV_ABI sceNetShowRoute6WithMemory();
int PS4_SYSV_ABI sceNetShowRouteForBuffer();
int PS4_SYSV_ABI sceNetShowRouteWithMemory();
int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how);
OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol);
int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags);
int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s);
int PS4_SYSV_ABI sceNetSyncCreate();
int PS4_SYSV_ABI sceNetSyncDestroy();
int PS4_SYSV_ABI sceNetSyncGet();
int PS4_SYSV_ABI sceNetSyncSignal();
int PS4_SYSV_ABI sceNetSyncWait();
int PS4_SYSV_ABI sceNetSysctl();
int PS4_SYSV_ABI sceNetTerm();
int PS4_SYSV_ABI sceNetThreadCreate();
int PS4_SYSV_ABI sceNetThreadExit();
int PS4_SYSV_ABI sceNetThreadJoin();
int PS4_SYSV_ABI sceNetUsleep();
int PS4_SYSV_ABI Func_0E707A589F751C68();
int PS4_SYSV_ABI sceNetEmulationGet();
int PS4_SYSV_ABI sceNetEmulationSet();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Net
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// error codes
constexpr int ORBIS_NET_CTL_ERROR_CALLBACK_MAX = 0x80412103;
constexpr int ORBIS_NET_CTL_ERROR_ID_NOT_FOUND = 0x80412104;
constexpr int ORBIS_NET_CTL_ERROR_INVALID_ID = 0x80412105;
constexpr int ORBIS_NET_CTL_ERROR_INVALID_ADDR = 0x80412107;
constexpr int ORBIS_NET_CTL_ERROR_INVALID_SIZE = 0x80412111;
constexpr int ORBIS_NET_CTL_ERROR_NOT_CONNECTED = 0x80412108;
constexpr int ORBIS_NET_CTL_ERROR_NOT_AVAIL = 0x80412109;
constexpr int ORBIS_NET_CTL_ERROR_NETWORK_DISABLED = 0x8041210D;
constexpr int ORBIS_NET_CTL_ERROR_DISCONNECT_REQ = 0x8041210E;
constexpr int ORBIS_NET_CTL_ERROR_ETHERNET_PLUGOUT = 0x80412115;
constexpr int ORBIS_NET_CTL_ERROR_WIFI_DEAUTHED = 0x80412116;
constexpr int ORBIS_NET_CTL_ERROR_WIFI_BEACON_LOST = 0x80412117;
// state codes
constexpr int ORBIS_NET_CTL_STATE_DISCONNECTED = 0;
constexpr int ORBIS_NET_CTL_STATE_CONNECTING = 1;
constexpr int ORBIS_NET_CTL_STATE_IPOBTAINING = 2;
constexpr int ORBIS_NET_CTL_STATE_IPOBTAINED = 3;
// event type
constexpr int ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED = 1;
constexpr int ORBIS_SCE_NET_CTL_EVENT_TYPE_DISCONNECT_REQ_FINISHED = 2;
constexpr int ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED = 3;
// get info codes
// device
constexpr int ORBIS_NET_CTL_DEVICE_WIRED = 0;
constexpr int ORBIS_NET_CTL_DEVICE_WIRELESS = 1;
// link
constexpr int ORBIS_NET_CTL_LINK_DISCONNECTED = 0;
constexpr int ORBIS_NET_CTL_LINK_CONNECTED = 1;
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/network/net_ctl_codes.h"
#include "core/libraries/network/net_ctl_obj.h"
#include "core/tls.h"
namespace Libraries::NetCtl {
NetCtlInternal::NetCtlInternal() = default;
NetCtlInternal::~NetCtlInternal() = default;
s32 NetCtlInternal::RegisterCallback(OrbisNetCtlCallback func, void* arg) {
std::scoped_lock lock{m_mutex};
// Find the next available slot
const auto it = std::ranges::find(callbacks, nullptr, &NetCtlCallback::func);
if (it == callbacks.end()) {
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
}
const int next_id = std::distance(callbacks.begin(), it);
callbacks[next_id].func = func;
callbacks[next_id].arg = arg;
return next_id;
}
s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg) {
std::scoped_lock lock{m_mutex};
// Find the next available slot
const auto it = std::ranges::find(nptool_callbacks, nullptr, &NetCtlCallbackForNpToolkit::func);
if (it == nptool_callbacks.end()) {
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
}
const int next_id = std::distance(nptool_callbacks.begin(), it);
nptool_callbacks[next_id].func = func;
nptool_callbacks[next_id].arg = arg;
return next_id;
}
void NetCtlInternal::CheckCallback() {
std::scoped_lock lock{m_mutex};
const auto event = EmulatorSettings.IsConnectedToNetwork()
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : callbacks) {
if (func != nullptr) {
func(event, arg);
}
}
}
void NetCtlInternal::CheckNpToolkitCallback() {
std::scoped_lock lock{m_mutex};
const auto event = EmulatorSettings.IsConnectedToNetwork()
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : nptool_callbacks) {
if (func != nullptr) {
func(event, arg);
}
}
}
} // namespace Libraries::NetCtl
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "common/types.h"
namespace Libraries::NetCtl {
using OrbisNetCtlCallback = PS4_SYSV_ABI void (*)(int eventType, void* arg);
using OrbisNetCtlCallbackForNpToolkit = PS4_SYSV_ABI void (*)(int eventType, void* arg);
struct NetCtlCallback {
OrbisNetCtlCallback func;
void* arg;
};
struct NetCtlCallbackForNpToolkit {
OrbisNetCtlCallbackForNpToolkit func;
void* arg;
};
class NetCtlInternal {
public:
explicit NetCtlInternal();
~NetCtlInternal();
s32 RegisterCallback(OrbisNetCtlCallback func, void* arg);
s32 RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg);
void CheckCallback();
void CheckNpToolkitCallback();
public:
std::array<NetCtlCallbackForNpToolkit, 8> nptool_callbacks{};
std::array<NetCtlCallback, 8> callbacks{};
std::mutex m_mutex;
};
} // namespace Libraries::NetCtl
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <ranges>
#include "common/assert.h"
#include "common/types.h"
#include "net_epoll.h"
namespace Libraries::Net {
u32 ConvertEpollEventsIn(u32 orbis_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) {
ret |= EPOLLIN;
}
if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) {
ret |= EPOLLOUT;
}
#endif
return ret;
}
u32 ConvertEpollEventsOut(u32 epoll_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((epoll_events & EPOLLIN) != 0) {
ret |= ORBIS_NET_EPOLLIN;
}
if ((epoll_events & EPOLLOUT) != 0) {
ret |= ORBIS_NET_EPOLLOUT;
}
if ((epoll_events & EPOLLERR) != 0) {
ret |= ORBIS_NET_EPOLLERR;
}
if ((epoll_events & EPOLLHUP) != 0) {
ret |= ORBIS_NET_EPOLLHUP;
}
#endif
return ret;
}
int EpollTable::CreateHandle(const char* name) {
std::scoped_lock lock{m_mutex};
if (auto it = std::ranges::find_if(epolls, [](const Epoll& e) { return e.Destroyed(); });
it != epolls.end()) {
*it = Epoll(name);
const auto ret = std::distance(epolls.begin(), it);
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
return ret;
} else {
epolls.emplace_back(name);
const auto ret = epolls.size() - 1;
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
return ret;
}
}
void EpollTable::DeleteHandle(int d) {
UNREACHABLE();
}
Epoll* EpollTable::GetEpoll(int epollid) {
std::scoped_lock lock{m_mutex};
if (epollid >= epolls.size() || epolls[epollid].Destroyed()) {
return nullptr;
}
return &epolls[epollid];
}
} // namespace Libraries::Net
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <deque>
#include <mutex>
#include <vector>
#ifdef _WIN32
#include <wepoll.h>
#endif
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
// ADD libepoll-shim if using freebsd!
#include <sys/epoll.h>
#include <unistd.h>
#endif
namespace Libraries::Net {
#ifdef _WIN32
using epoll_handle = HANDLE;
#else
using epoll_handle = int;
#endif
struct Epoll {
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
std::string name;
epoll_handle epoll_fd;
std::deque<u32> async_resolutions{};
explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) {
#ifdef _WIN32
ASSERT(epoll_fd != nullptr);
#else
ASSERT(epoll_fd != -1);
#endif
if (name_ == nullptr) {
name = "anon";
}
}
bool Destroyed() const noexcept {
return destroyed;
}
void Destroy() noexcept {
events.clear();
#ifdef _WIN32
epoll_close(epoll_fd);
epoll_fd = nullptr;
#else
close(epoll_fd);
epoll_fd = -1;
#endif
name = "";
destroyed = true;
}
private:
bool destroyed{};
};
u32 ConvertEpollEventsIn(u32 orbis_events);
u32 ConvertEpollEventsOut(u32 epoll_events);
class EpollTable {
public:
EpollTable() = default;
virtual ~EpollTable() = default;
int CreateHandle(const char* name);
void DeleteHandle(int d);
Epoll* GetEpoll(int d);
private:
std::vector<Epoll> epolls;
std::mutex m_mutex;
};
} // namespace Libraries::Net
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// net errno codes
constexpr int ORBIS_NET_EPERM = 1;
constexpr int ORBIS_NET_ENOENT = 2;
constexpr int ORBIS_NET_EINTR = 4;
constexpr int ORBIS_NET_EBADF = 9;
constexpr int ORBIS_NET_ENOMEM = 12;
constexpr int ORBIS_NET_EACCES = 13;
constexpr int ORBIS_NET_EFAULT = 14;
constexpr int ORBIS_NET_ENOTBLK = 15;
constexpr int ORBIS_NET_EBUSY = 16;
constexpr int ORBIS_NET_EEXIST = 17;
constexpr int ORBIS_NET_ENODEV = 19;
constexpr int ORBIS_NET_EINVAL = 22;
constexpr int ORBIS_NET_ENFILE = 23;
constexpr int ORBIS_NET_EMFILE = 24;
constexpr int ORBIS_NET_ENOSPC = 28;
constexpr int ORBIS_NET_EPIPE = 32;
constexpr int ORBIS_NET_EAGAIN = 35;
constexpr int ORBIS_NET_EWOULDBLOCK = 35;
constexpr int ORBIS_NET_EINPROGRESS = 36;
constexpr int ORBIS_NET_EALREADY = 37;
constexpr int ORBIS_NET_ENOTSOCK = 38;
constexpr int ORBIS_NET_EDESTADDRREQ = 39;
constexpr int ORBIS_NET_EMSGSIZE = 40;
constexpr int ORBIS_NET_EPROTOTYPE = 41;
constexpr int ORBIS_NET_ENOPROTOOPT = 42;
constexpr int ORBIS_NET_EPROTONOSUPPORT = 43;
constexpr int ORBIS_NET_EOPNOTSUPP = 45;
constexpr int ORBIS_NET_EAFNOSUPPORT = 47;
constexpr int ORBIS_NET_EADDRINUSE = 48;
constexpr int ORBIS_NET_EADDRNOTAVAIL = 49;
constexpr int ORBIS_NET_ENETDOWN = 50;
constexpr int ORBIS_NET_ENETUNREACH = 51;
constexpr int ORBIS_NET_ENETRESET = 52;
constexpr int ORBIS_NET_ECONNABORTED = 53;
constexpr int ORBIS_NET_ECONNRESET = 54;
constexpr int ORBIS_NET_ENOBUFS = 55;
constexpr int ORBIS_NET_EISCONN = 56;
constexpr int ORBIS_NET_ENOTCONN = 57;
constexpr int ORBIS_NET_ETOOMANYREFS = 59;
constexpr int ORBIS_NET_ETIMEDOUT = 60;
constexpr int ORBIS_NET_ECONNREFUSED = 61;
constexpr int ORBIS_NET_ELOOP = 62;
constexpr int ORBIS_NET_ENAMETOOLONG = 63;
constexpr int ORBIS_NET_EHOSTDOWN = 64;
constexpr int ORBIS_NET_EHOSTUNREACH = 65;
constexpr int ORBIS_NET_ENOTEMPTY = 66;
constexpr int ORBIS_NET_EPROCUNAVAIL = 76;
constexpr int ORBIS_NET_ECANCELED = 87;
constexpr int ORBIS_NET_EPROTO = 92;
constexpr int ORBIS_NET_EADHOC = 160;
constexpr int ORBIS_NET_EINACTIVEDISABLED = 163;
constexpr int ORBIS_NET_ENODATA = 164;
constexpr int ORBIS_NET_EDESC = 165;
constexpr int ORBIS_NET_EDESCTIMEDOUT = 166;
constexpr int ORBIS_NET_ENETINTR = 167;
constexpr int ORBIS_NET_ENOTINIT = 200;
constexpr int ORBIS_NET_ENOLIBMEM = 201;
constexpr int ORBIS_NET_ECALLBACK = 203;
constexpr int ORBIS_NET_EINTERNAL = 204;
constexpr int ORBIS_NET_ERETURN = 205;
constexpr int ORBIS_NET_ENOALLOCMEM = 206;
// errno for dns resolver
constexpr int ORBIS_NET_RESOLVER_EINTERNAL = 220;
constexpr int ORBIS_NET_RESOLVER_EBUSY = 221;
constexpr int ORBIS_NET_RESOLVER_ENOSPACE = 222;
constexpr int ORBIS_NET_RESOLVER_EPACKET = 223;
constexpr int ORBIS_NET_RESOLVER_ENODNS = 225;
constexpr int ORBIS_NET_RESOLVER_ETIMEDOUT = 226;
constexpr int ORBIS_NET_RESOLVER_ENOSUPPORT = 227;
constexpr int ORBIS_NET_RESOLVER_EFORMAT = 228;
constexpr int ORBIS_NET_RESOLVER_ESERVERFAILURE = 229;
constexpr int ORBIS_NET_RESOLVER_ENOHOST = 230;
constexpr int ORBIS_NET_RESOLVER_ENOTIMPLEMENTED = 231;
constexpr int ORBIS_NET_RESOLVER_ESERVERREFUSED = 232;
constexpr int ORBIS_NET_RESOLVER_ENORECORD = 233;
constexpr int ORBIS_NET_RESOLVER_EALIGNMENT = 234;
// error codes
constexpr int ORBIS_NET_ERROR_BASE = 0x80410100; // not existed used for calculation
constexpr int ORBIS_NET_ERROR_EPERM = 0x80410101;
constexpr int ORBIS_NET_ERROR_ENOENT = 0x80410102;
constexpr int ORBIS_NET_ERROR_EINTR = 0x80410104;
constexpr int ORBIS_NET_ERROR_EBADF = 0x80410109;
constexpr int ORBIS_NET_ERROR_ENOMEM = 0x8041010c;
constexpr int ORBIS_NET_ERROR_EACCES = 0x8041010d;
constexpr int ORBIS_NET_ERROR_EFAULT = 0x8041010e;
constexpr int ORBIS_NET_ERROR_ENOTBLK = 0x8041010f;
constexpr int ORBIS_NET_ERROR_EEXIST = 0x80410111;
constexpr int ORBIS_NET_ERROR_ENODEV = 0x80410113;
constexpr int ORBIS_NET_ERROR_EINVAL = 0x80410116;
constexpr int ORBIS_NET_ERROR_ENFILE = 0x80410117;
constexpr int ORBIS_NET_ERROR_EMFILE = 0x80410118;
constexpr int ORBIS_NET_ERROR_ENOSPC = 0x8041011c;
constexpr int ORBIS_NET_ERROR_EPIPE = 0x80410120;
constexpr int ORBIS_NET_ERROR_EAGAIN = 0x80410123;
constexpr int ORBIS_NET_ERROR_EWOULDBLOCK = 0x80410123;
constexpr int ORBIS_NET_ERROR_EINPROGRESS = 0x80410124;
constexpr int ORBIS_NET_ERROR_EALREADY = 0x80410125;
constexpr int ORBIS_NET_ERROR_ENOTSOCK = 0x80410126;
constexpr int ORBIS_NET_ERROR_EDESTADDRREQ = 0x80410127;
constexpr int ORBIS_NET_ERROR_EMSGSIZE = 0x80410128;
constexpr int ORBIS_NET_ERROR_EPROTOTYPE = 0x80410129;
constexpr int ORBIS_NET_ERROR_ENOPROTOOPT = 0x8041012a;
constexpr int ORBIS_NET_ERROR_EPROTONOSUPPORT = 0x8041012b;
constexpr int ORBIS_NET_ERROR_EOPNOTSUPP = 0x8041012d;
constexpr int ORBIS_NET_ERROR_EPFNOSUPPORT = 0x8041012e;
constexpr int ORBIS_NET_ERROR_EAFNOSUPPORT = 0x8041012f;
constexpr int ORBIS_NET_ERROR_EADDRINUSE = 0x80410130;
constexpr int ORBIS_NET_ERROR_EADDRNOTAVAIL = 0x80410131;
constexpr int ORBIS_NET_ERROR_ENETDOWN = 0x80410132;
constexpr int ORBIS_NET_ERROR_ENETUNREACH = 0x80410133;
constexpr int ORBIS_NET_ERROR_ENETRESET = 0x80410134;
constexpr int ORBIS_NET_ERROR_ECONNABORTED = 0x80410135;
constexpr int ORBIS_NET_ERROR_ECONNRESET = 0x80410136;
constexpr int ORBIS_NET_ERROR_ENOBUFS = 0x80410137;
constexpr int ORBIS_NET_ERROR_EISCONN = 0x80410138;
constexpr int ORBIS_NET_ERROR_ENOTCONN = 0x80410139;
constexpr int ORBIS_NET_ERROR_ESHUTDOWN = 0x8041013a;
constexpr int ORBIS_NET_ERROR_ETOOMANYREFS = 0x8041013b;
constexpr int ORBIS_NET_ERROR_ETIMEDOUT = 0x8041013c;
constexpr int ORBIS_NET_ERROR_ECONNREFUSED = 0x8041013d;
constexpr int ORBIS_NET_ERROR_ELOOP = 0x8041013e;
constexpr int ORBIS_NET_ERROR_ENAMETOOLONG = 0x8041013f;
constexpr int ORBIS_NET_ERROR_EHOSTDOWN = 0x80410140;
constexpr int ORBIS_NET_ERROR_EHOSTUNREACH = 0x80410141;
constexpr int ORBIS_NET_ERROR_ENOTEMPTY = 0x80410142;
constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014C;
constexpr int ORBIS_NET_ERROR_ECANCELED = 0x80410157;
constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015C;
constexpr int ORBIS_NET_ERROR_EADHOC = 0x804101a0;
constexpr int ORBIS_NET_ERROR_ERESERVED161 = 0x804101a1;
constexpr int ORBIS_NET_ERROR_ERESERVED162 = 0x804101a2;
constexpr int ORBIS_NET_ERROR_EINACTIVEDISABLED = 0x804101a3;
constexpr int ORBIS_NET_ERROR_ENODATA = 0x804101a4;
constexpr int ORBIS_NET_ERROR_EDESC = 0x804101a5;
constexpr int ORBIS_NET_ERROR_EDESCTIMEDOUT = 0x804101a6;
constexpr int ORBIS_NET_ERROR_ENOTINIT = 0x804101c8;
constexpr int ORBIS_NET_ERROR_ENOLIBMEM = 0x804101c9;
constexpr int ORBIS_NET_ERROR_ECALLBACK = 0x804101cb;
constexpr int ORBIS_NET_ERROR_EINTERNAL = 0x804101cc;
constexpr int ORBIS_NET_ERROR_ERETURN = 0x804101cd;
constexpr int ORBIS_NET_ERROR_ENOALLOCMEM = 0x804101ce;
constexpr int ORBIS_NET_ERROR_RESOLVER_EINTERNAL = 0x804101dc;
constexpr int ORBIS_NET_ERROR_RESOLVER_EBUSY = 0x804101dd;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSPACE = 0x804101de;
constexpr int ORBIS_NET_ERROR_RESOLVER_EPACKET = 0x804101df;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENODNS = 0x804101e1;
constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT = 0x804101e3;
constexpr int ORBIS_NET_ERROR_RESOLVER_EFORMAT = 0x804101e4;
constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE = 0x804101e5;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOHOST = 0x804101e6;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x804101e7;
constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERREFUSED = 0x804101e8;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENORECORD = 0x804101e9;
constexpr int ORBIS_NET_ERROR_RESOLVER_EALIGNMENT = 0x804101ea;
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "net_error.h"
#include "net_resolver.h"
#include "net_util.h"
namespace Libraries::Net {
int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry,
int flags) {
std::scoped_lock lock{m_mutex};
if (async_resolution) {
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY;
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
}
async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags};
return ORBIS_OK;
}
void Resolver::Resolve() {
if (!EmulatorSettings.IsConnectedToNetwork()) {
resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
return;
}
if (async_resolution) {
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
resolution_error = ret;
if (ret != ORBIS_OK) {
// Resolver errors are stored as ORBIS_NET_ERROR values.
resolution_error = -ret | ORBIS_NET_ERROR_BASE;
}
} else {
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
}
}
} // namespace Libraries::Net
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <mutex>
#include <vector>
namespace Libraries::Net {
struct Resolver {
public:
Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {}
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
void Resolve();
int resolution_error = ORBIS_OK;
private:
struct AsyncResolution {
const char* hostname;
OrbisNetInAddr* addr;
int timeout;
int retry;
int flags;
};
std::string name;
int poolid;
int flags;
std::optional<AsyncResolution> async_resolution{};
std::mutex m_mutex;
};
} // namespace Libraries::Net
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <cstring>
#include <upnperrors.h>
#include "common/logging/log.h"
#include "net_upnp.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <net/if.h>
#endif
namespace Libraries::Net {
UPnPClient& UPnPClient::Instance() {
static UPnPClient instance;
return instance;
}
UPnPClient::~UPnPClient() {
if (m_thread.joinable())
m_thread.join();
if (m_available.load())
FreeUPNPUrls(&m_urls);
}
void UPnPClient::Start() {
if (!m_p2p_features_enabled.load()) {
LOG_INFO(Lib_Net, "UPNP: skipped because Matching2 is disabled");
return;
}
bool expected = false;
if (!m_started.compare_exchange_strong(expected, true))
return;
LOG_INFO(Lib_Net, "UPNP: starting discovery thread");
m_thread = std::thread(&UPnPClient::DiscoverThread, this);
}
void UPnPClient::SetP2PFeaturesEnabled(bool enabled) {
m_p2p_features_enabled.store(enabled);
}
bool UPnPClient::WaitReady(int timeout_ms) {
std::unique_lock lock(m_mutex);
m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { return m_done.load(); });
return m_available.load();
}
bool UPnPClient::IsAvailable() const {
return m_available.load();
}
u32 UPnPClient::GetExternalIp() const {
return m_external_ip_nbo.load();
}
std::string UPnPClient::GetExternalIpString() const {
return m_available.load() ? std::string(m_external_ip_str) : std::string{};
}
u16 UPnPClient::GetExternalPort() const {
return m_external_port.load();
}
void UPnPClient::DiscoverThread() {
int error = 0;
UPNPDev* devlist = upnpDiscover(2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error);
if (!devlist) {
LOG_INFO(Lib_Net, "UPNP: no IGD found (error={})", error);
m_done = true;
m_cv.notify_all();
return;
}
char wan_addr[40]{};
const int ret = UPNP_GetValidIGD(devlist, &m_urls, &m_data, m_lan_addr, sizeof(m_lan_addr),
wan_addr, sizeof(wan_addr));
freeUPNPDevlist(devlist);
if (ret != 1) {
LOG_INFO(Lib_Net, "UPNP: no valid IGD (ret={})", ret);
m_done = true;
m_cv.notify_all();
return;
}
char external_ip[40]{};
if (UPNP_GetExternalIPAddress(m_urls.controlURL, m_data.first.servicetype, external_ip) !=
UPNPCOMMAND_SUCCESS) {
LOG_WARNING(Lib_Net, "UPNP: IGD found but failed to get external IP");
FreeUPNPUrls(&m_urls);
m_done = true;
m_cv.notify_all();
return;
}
u32 ip_nbo = 0;
inet_pton(AF_INET, external_ip, &ip_nbo);
m_external_ip_nbo.store(ip_nbo);
std::strncpy(m_external_ip_str, external_ip, sizeof(m_external_ip_str) - 1);
m_available = true;
LOG_INFO(Lib_Net, "UPNP: IGD found - external IP={} lan={} (port mapping available)",
external_ip, m_lan_addr);
m_done = true;
m_cv.notify_all();
}
void UPnPClient::AddMapping(u16 port) {
if (!m_p2p_features_enabled.load())
return;
if (!m_available.load())
return;
const std::string port_str = std::to_string(port);
const int ret =
UPNP_AddPortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(),
port_str.c_str(), m_lan_addr, "shadPS4", "UDP", nullptr, "0");
if (ret == UPNPCOMMAND_SUCCESS) {
m_external_port.store(port);
LOG_INFO(Lib_Net, "UPNP: mapped UDP port {}", port);
} else {
// Store the requested port as best-guess even on failure so callers still get a value.
m_external_port.store(port);
LOG_WARNING(Lib_Net, "UPNP: failed to map port {} ({})", port, strupnperror(ret));
}
}
void UPnPClient::RemoveMapping(u16 port) {
if (!m_available.load())
return;
const std::string port_str = std::to_string(port);
UPNP_DeletePortMapping(m_urls.controlURL, m_data.first.servicetype, port_str.c_str(), "UDP",
nullptr);
LOG_INFO(Lib_Net, "UPNP: removed UDP port mapping {}", port);
}
} // namespace Libraries::Net
@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include "common/types.h"
#include <miniupnpc.h>
#include <upnpcommands.h>
namespace Libraries::Net {
class UPnPClient {
public:
static UPnPClient& Instance();
// Kick off async IGD discovery. Safe to call multiple times — only the first call has effect.
void Start();
void SetP2PFeaturesEnabled(bool enabled);
// Block until discovery completes or timeout_ms elapses. Returns true if IGD was found.
bool WaitReady(int timeout_ms);
bool IsAvailable() const;
// External IP in network byte order as reported by the IGD. 0 if not yet known.
u32 GetExternalIp() const;
// External IP as a dotted-decimal string. Empty if not yet known.
std::string GetExternalIpString() const;
// External port the IGD mapped for us. Matches the internal port unless the router remapped it.
u16 GetExternalPort() const;
void AddMapping(u16 port);
void RemoveMapping(u16 port);
private:
UPnPClient() = default;
~UPnPClient();
UPnPClient(const UPnPClient&) = delete;
UPnPClient& operator=(const UPnPClient&) = delete;
void DiscoverThread();
std::atomic<bool> m_started{false};
std::atomic<bool> m_p2p_features_enabled{true};
std::atomic<bool> m_done{false};
std::atomic<bool> m_available{false};
std::atomic<u32> m_external_ip_nbo{0};
std::atomic<u16> m_external_port{0};
char m_external_ip_str[16]{};
char m_lan_addr[64]{};
UPNPUrls m_urls{};
IGDdatas m_data{};
std::mutex m_mutex;
std::condition_variable m_cv;
std::thread m_thread;
};
} // namespace Libraries::Net
@@ -0,0 +1,416 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <Ws2tcpip.h>
#include <iphlpapi.h>
#include <winsock2.h>
typedef SOCKET net_socket;
typedef int socklen_t;
#else
#include <cerrno>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
typedef int net_socket;
#endif
#if defined(__APPLE__)
#include <net/if_dl.h>
#include <net/route.h>
#endif
#if defined(__linux__) || defined(__FreeBSD__)
#include <fstream>
#include <iostream>
#include <sstream>
#endif
#include <map>
#include <memory>
#include <mutex>
#include <vector>
#include <string.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "net.h"
#include "net_error.h"
#include "net_util.h"
namespace NetUtil {
const std::array<u8, 6>& NetUtilInternal::GetEthernetAddr() const {
return ether_address;
}
bool NetUtilInternal::RetrieveEthernetAddr() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
std::vector<u8> adapter_infos(sizeof(IP_ADAPTER_INFO));
ULONG size_infos = sizeof(IP_ADAPTER_INFO);
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
ERROR_BUFFER_OVERFLOW)
adapter_infos.resize(size_infos);
if (GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data()), &size_infos) ==
NO_ERROR &&
size_infos) {
PIP_ADAPTER_INFO info = reinterpret_cast<PIP_ADAPTER_INFO>(adapter_infos.data());
memcpy(ether_address.data(), info[0].Address, 6);
return true;
}
#elif defined(__APPLE__)
ifaddrs* ifap;
if (getifaddrs(&ifap) == 0) {
ifaddrs* p;
for (p = ifap; p; p = p->ifa_next) {
if (p->ifa_addr->sa_family == AF_LINK) {
sockaddr_dl* sdp = reinterpret_cast<sockaddr_dl*>(p->ifa_addr);
memcpy(ether_address.data(), sdp->sdl_data + sdp->sdl_nlen, 6);
freeifaddrs(ifap);
return true;
}
}
freeifaddrs(ifap);
}
#elif defined(__FreeBSD__)
// todo
#else
ifreq ifr;
ifconf ifc;
char buf[1024];
int success = 0;
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock == -1)
return false;
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1)
return false;
ifreq* it = ifc.ifc_req;
const ifreq* const end = it + (ifc.ifc_len / sizeof(ifreq));
for (; it != end; ++it) {
strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
if (!(ifr.ifr_flags & IFF_LOOPBACK)) {
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
success = 1;
break;
}
}
}
}
close(sock);
if (success) {
memcpy(ether_address.data(), ifr.ifr_hwaddr.sa_data, 6);
return true;
}
#endif
return false;
}
const std::string& NetUtilInternal::GetDefaultGateway() const {
return default_gateway;
}
bool NetUtilInternal::RetrieveDefaultGateway() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
ULONG flags = GAA_FLAG_INCLUDE_GATEWAYS;
ULONG family = AF_INET; // Only IPv4
ULONG buffer_size = 15000;
std::vector<BYTE> buffer(buffer_size);
PIP_ADAPTER_ADDRESSES adapter_addresses =
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
if (result != NO_ERROR) {
return false;
}
for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; adapter != nullptr;
adapter = adapter->Next) {
if (adapter->OperStatus != IfOperStatusUp)
continue;
IP_ADAPTER_GATEWAY_ADDRESS_LH* gateway = adapter->FirstGatewayAddress;
while (gateway) {
sockaddr* sa = gateway->Address.lpSockaddr;
if (sa->sa_family == AF_INET) {
char str[INET_ADDRSTRLEN];
sockaddr_in* sa_in = reinterpret_cast<sockaddr_in*>(sa);
if (inet_ntop(AF_INET, &sa_in->sin_addr, str, sizeof(str))) {
this->default_gateway = str;
return true;
}
}
gateway = gateway->Next;
}
}
return false;
#elif defined(__APPLE__)
// adapted from
// https://github.com/seladb/PcapPlusPlus/blob/a49a79e0b67b402ad75ffa96c1795def36df75c8/Pcap%2B%2B/src/PcapLiveDevice.cpp#L1236
// route message struct for communication in APPLE device
struct BSDRoutingMessage {
struct rt_msghdr header;
char messageSpace[512];
};
struct BSDRoutingMessage routingMessage;
// It creates a raw socket that can be used for routing-related operations
int sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
if (sockfd < 0) {
return false;
}
memset(reinterpret_cast<char*>(&routingMessage), 0, sizeof(routingMessage));
routingMessage.header.rtm_msglen = sizeof(struct rt_msghdr);
routingMessage.header.rtm_version = RTM_VERSION;
routingMessage.header.rtm_type = RTM_GET;
routingMessage.header.rtm_addrs = RTA_DST | RTA_NETMASK;
routingMessage.header.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
routingMessage.header.rtm_msglen += 2 * sizeof(sockaddr_in);
if (write(sockfd, reinterpret_cast<char*>(&routingMessage), routingMessage.header.rtm_msglen) <
0) {
return false;
}
// Read the response from the route socket
if (read(sockfd, reinterpret_cast<char*>(&routingMessage), sizeof(routingMessage)) < 0) {
return false;
}
struct in_addr* gateAddr = nullptr;
struct sockaddr* sa = nullptr;
char* spacePtr = (reinterpret_cast<char*>(&routingMessage.header + 1));
auto rtmAddrs = routingMessage.header.rtm_addrs;
int index = 1;
auto roundUpClosestMultiple = [](int multiple, int num) {
return ((num + multiple - 1) / multiple) * multiple;
};
while (rtmAddrs) {
if (rtmAddrs & 1) {
sa = reinterpret_cast<sockaddr*>(spacePtr);
if (index == RTA_GATEWAY) {
gateAddr = &((sockaddr_in*)sa)->sin_addr;
break;
}
spacePtr += sa->sa_len > 0 ? roundUpClosestMultiple(sizeof(uint32_t), sa->sa_len)
: sizeof(uint32_t);
}
index++;
rtmAddrs >>= 1;
}
if (gateAddr == nullptr) {
return false;
}
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, gateAddr, str, sizeof(str));
this->default_gateway = str;
return true;
#elif defined(__FreeBSD__)
return true;
#else
std::ifstream route{"/proc/net/route"};
std::string line;
std::getline(route, line);
while (std::getline(route, line)) {
std::istringstream iss{line};
std::string iface, destination, gateway;
int flags;
iss >> iface >> destination >> gateway >> std::hex >> flags;
if (destination == "00000000") {
u64 default_gateway{};
std::stringstream ss;
ss << std::hex << gateway;
ss >> default_gateway;
char str[INET_ADDRSTRLEN];
in_addr addr;
addr.s_addr = default_gateway;
inet_ntop(AF_INET, &addr, str, sizeof(str));
this->default_gateway = str;
return true;
}
}
#endif
return false;
}
const std::string& NetUtilInternal::GetNetmask() const {
return netmask;
}
bool NetUtilInternal::RetrieveNetmask() {
std::scoped_lock lock{m_mutex};
char netmaskStr[INET_ADDRSTRLEN];
auto success = false;
#ifdef _WIN32
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
ULONG family = AF_INET; // Only IPv4
ULONG buffer_size = 15000;
std::vector<BYTE> buffer(buffer_size);
auto adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
DWORD result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
if (result == ERROR_BUFFER_OVERFLOW) {
buffer.resize(buffer_size);
adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data());
result = GetAdaptersAddresses(family, flags, nullptr, adapter_addresses, &buffer_size);
}
if (result != NO_ERROR)
return false;
for (auto adapter = adapter_addresses; adapter != nullptr; adapter = adapter->Next) {
// Skip loopback and down interfaces
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK || adapter->OperStatus != IfOperStatusUp)
continue;
for (auto unicast = adapter->FirstUnicastAddress; unicast != nullptr;
unicast = unicast->Next) {
if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
ULONG prefix_length = unicast->OnLinkPrefixLength;
ULONG mask = prefix_length == 0 ? 0 : 0xFFFFFFFF << (32 - prefix_length);
in_addr mask_addr{};
mask_addr.S_un.S_addr = htonl(mask);
if (inet_ntop(AF_INET, &mask_addr, netmaskStr, INET_ADDRSTRLEN)) {
success = true;
}
}
}
}
#else
ifaddrs* ifap;
if (getifaddrs(&ifap) == 0) {
ifaddrs* p;
for (p = ifap; p; p = p->ifa_next) {
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET) {
auto sa = reinterpret_cast<sockaddr_in*>(p->ifa_netmask);
inet_ntop(AF_INET, &sa->sin_addr, netmaskStr, INET_ADDRSTRLEN);
success = true;
}
}
}
freeifaddrs(ifap);
#endif
if (success) {
netmask = netmaskStr;
}
return success;
}
const std::string& NetUtilInternal::GetIp() const {
return ip;
}
u32 NetUtilInternal::GetExternalIp() const {
return external_ip;
}
void NetUtilInternal::SetExternalIp(u32 addr) {
external_ip = addr;
}
u32 NetUtilInternal::GetNatType() const {
return nat_type;
}
bool NetUtilInternal::RetrieveIp() {
std::scoped_lock lock{m_mutex};
auto sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
return false;
}
sockaddr_in sa{};
socklen_t sa_len{sizeof(sa)};
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr("1.1.1.1");
sa.sin_port = htons(80);
#ifdef _WIN32
#define close closesocket
#endif
if (connect(sockfd, (sockaddr*)&sa, sa_len) == -1) {
close(sockfd);
return false;
}
if (getsockname(sockfd, (struct sockaddr*)&sa, &sa_len) == -1) {
close(sockfd);
return false;
}
char netmaskStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sa.sin_addr, netmaskStr, INET_ADDRSTRLEN);
ip = netmaskStr;
close(sockfd);
#ifdef _WIN32
#undef close
#endif
return true;
}
int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr) {
const addrinfo hints = {
.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG,
.ai_family = AF_INET,
};
addrinfo* info = nullptr;
auto gai_result = getaddrinfo(hostname, nullptr, &hints, &info);
auto ret = ORBIS_OK;
if (gai_result != 0) {
// handle more errors
LOG_ERROR(Lib_Net, "address resolution for {} failed: {}", hostname, gai_result);
ret = ORBIS_NET_ERETURN;
} else {
ASSERT(info && info->ai_addr);
in_addr resolved_addr = ((sockaddr_in*)info->ai_addr)->sin_addr;
LOG_DEBUG(Lib_Net, "resolved address for {}: {}", hostname, inet_ntoa(resolved_addr));
addr->inaddr_addr = resolved_addr.s_addr;
}
freeaddrinfo(info);
return ret;
}
} // namespace NetUtil
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "common/types.h"
namespace Libraries::Net {
struct OrbisNetInAddr;
}
namespace NetUtil {
class NetUtilInternal {
public:
explicit NetUtilInternal() = default;
~NetUtilInternal() = default;
private:
std::array<u8, 6> ether_address{};
std::string default_gateway{};
std::string netmask{};
std::string ip{};
u32 external_ip{0};
u32 nat_type{0};
std::mutex m_mutex;
public:
const std::array<u8, 6>& GetEthernetAddr() const;
const std::string& GetDefaultGateway() const;
const std::string& GetNetmask() const;
const std::string& GetIp() const;
u32 GetExternalIp() const;
void SetExternalIp(u32 addr);
u32 GetNatType() const;
bool RetrieveEthernetAddr();
bool RetrieveDefaultGateway();
bool RetrieveNetmask();
bool RetrieveIp();
int ResolveHostname(const char* hostname, Libraries::Net::OrbisNetInAddr* addr);
};
} // namespace NetUtil
@@ -0,0 +1,758 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <Ws2tcpip.h>
#include <iphlpapi.h>
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif
#include <common/singleton.h>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/net_ctl_codes.h"
#include "core/libraries/network/netctl.h"
#include "net_util.h"
namespace Libraries::NetCtl {
static NetCtlInternal netctl;
int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweClearEventIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweFinishInternetConnectionTestIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweGetInfoIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweRegisterCallbackIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweSetInternetConnectionTestResultIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetBweUnregisterCallbackIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetInfoV6() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetResultV6() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetStateV6() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlCheckCallback() {
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlCheckCallbackForLibIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlClearEventForLibIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlClearEventIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlConnectConfIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlConnectIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlConnectWithRetryIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlDisableBandwidthManagementIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlDisconnectIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlEnableBandwidthManagementIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetBandwidthInfoIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetEtherLinkMode() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetIfStat() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
LOG_DEBUG(Lib_NetCtl, "code = {}", code);
if (!EmulatorSettings.IsConnectedToNetwork()) {
return ORBIS_NET_CTL_ERROR_NOT_CONNECTED;
}
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
switch (code) {
case ORBIS_NET_CTL_INFO_DEVICE:
info->device = ORBIS_NET_CTL_DEVICE_WIRED;
break;
case ORBIS_NET_CTL_INFO_ETHER_ADDR: {
netinfo->RetrieveEthernetAddr();
memcpy(info->ether_addr.data, netinfo->GetEthernetAddr().data(), 6);
} break;
case ORBIS_NET_CTL_INFO_MTU:
info->mtu = 1500; // default value
break;
case ORBIS_NET_CTL_INFO_LINK:
info->link = EmulatorSettings.IsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED
: ORBIS_NET_CTL_LINK_DISCONNECTED;
break;
case ORBIS_NET_CTL_INFO_IP_ADDRESS: {
strcpy(info->ip_address,
"127.0.0.1"); // placeholder in case ip retrieval failed
auto success = netinfo->RetrieveIp();
if (success) {
strncpy(info->ip_address, netinfo->GetIp().data(), sizeof(info->ip_address));
} else {
LOG_WARNING(Lib_NetCtl, "local ip: failed to retrieve");
}
break;
}
case ORBIS_NET_CTL_INFO_NETMASK: {
auto success = netinfo->RetrieveNetmask();
if (success) {
strncpy(info->netmask, netinfo->GetNetmask().data(), sizeof(info->netmask));
LOG_DEBUG(Lib_NetCtl, "netmask: {}", info->netmask);
} else {
LOG_WARNING(Lib_NetCtl, "netmask: failed to retrieve");
}
break;
}
case ORBIS_NET_CTL_INFO_DEFAULT_ROUTE: {
auto success = netinfo->RetrieveDefaultGateway();
if (success) {
strncpy(info->default_route, netinfo->GetDefaultGateway().data(),
sizeof(info->default_route));
LOG_DEBUG(Lib_NetCtl, "default gateway: {}", info->default_route);
} else {
LOG_WARNING(Lib_NetCtl, "default gateway: failed to retrieve");
}
break;
}
case ORBIS_NET_CTL_INFO_HTTP_PROXY_CONFIG:
info->http_proxy_config = 0; // off
LOG_DEBUG(Lib_NetCtl, "http proxy config: {}", info->http_proxy_config);
break;
case ORBIS_NET_CTL_INFO_PRIMARY_DNS:
strcpy(info->primary_dns, "1.1.1.1");
LOG_DEBUG(Lib_NetCtl, "http primary dns: {}", info->primary_dns);
break;
case ORBIS_NET_CTL_INFO_SECONDARY_DNS:
strcpy(info->secondary_dns, "1.1.1.1");
LOG_DEBUG(Lib_NetCtl, "http secondary dns: {}", info->secondary_dns);
break;
case ORBIS_NET_CTL_INFO_HTTP_PROXY_SERVER:
info->http_proxy_server[0] = '\0';
LOG_DEBUG(Lib_NetCtl, "http proxy server: none");
break;
case ORBIS_NET_CTL_INFO_HTTP_PROXY_PORT:
info->http_proxy_port = 0;
LOG_DEBUG(Lib_NetCtl, "http proxy config: {}", info->http_proxy_port);
break;
case ORBIS_NET_CTL_INFO_IP_CONFIG:
info->ip_config = 1; // static
LOG_DEBUG(Lib_NetCtl, "ip config: {}", info->ip_config);
break;
case ORBIS_NET_CTL_INFO_DHCP_HOSTNAME:
LOG_DEBUG(Lib_NetCtl, "dhcp hostname: none");
break;
default:
LOG_ERROR(Lib_NetCtl, "{} unsupported code", code);
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetInfoIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetInfoV6IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetNatInfo(OrbisNetCtlNatInfo* nat_info) {
if (!nat_info) {
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
}
if (nat_info->size != sizeof(OrbisNetCtlNatInfo)) {
return ORBIS_NET_CTL_ERROR_INVALID_SIZE;
}
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
nat_info->nat_type = netinfo->GetNatType();
const u32 ext_ip = netinfo->GetExternalIp();
if (ext_ip != 0) {
nat_info->stun_status = 1;
nat_info->mapped_addr = ext_ip;
} else {
nat_info->stun_status = 0;
nat_info->mapped_addr = inet_addr("127.0.0.1");
if (netinfo->RetrieveIp()) {
nat_info->mapped_addr = inet_addr(netinfo->GetIp().c_str());
}
}
LOG_DEBUG(Lib_NetCtl, "stun_status={} nat_type={} mapped_addr={:#x}", nat_info->stun_status,
nat_info->nat_type, nat_info->mapped_addr);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetNatInfoIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode) {
if (!errorCode) {
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
}
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called eventType = {} ", eventType);
*errorCode = 0;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetResultIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetResultV6IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetScanInfoByBssidIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidListScanIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetState(int* state) {
const auto connected = EmulatorSettings.IsConnectedToNetwork();
LOG_DEBUG(Lib_NetCtl, "connected = {}", connected);
const auto current_state =
connected ? ORBIS_NET_CTL_STATE_IPOBTAINED : ORBIS_NET_CTL_STATE_DISCONNECTED;
*state = current_state;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetState2IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetStateIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetStateV6IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlGetWifiType() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlInit() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid) {
if (!func || !cid) {
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
}
s32 result = netctl.RegisterCallback(func, arg);
if (result < 0) {
return result;
} else {
*cid = result;
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForLibIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallbackIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlScanIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlSetErrorNotificationEnabledIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlSetStunWithPaddingFlagIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlTerm() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForLibIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6IpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnsetStunWithPaddingFlagIpcInt() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI Func_D8DCB6973537A3DC() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit() {
LOG_DEBUG(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func,
void* arg, int* cid) {
if (!func || !cid) {
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
}
s32 result = netctl.RegisterNpToolkitCallback(func, arg);
if (result < 0) {
return result;
} else {
*cid = result;
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForNpToolkit() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApCheckCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApClearEvent() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApGetConnectInfo() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApGetInfo() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApGetResult() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApGetState() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApInit() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRegisterCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApStop() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApTerm() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApUnregisterCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKey() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKeyForQa() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetry() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetryPid() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRestart() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpCheckCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpClearEvent() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpGetInfo() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpGetResult() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpGetState() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpRegisterCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpStart() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpStartConf() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpStartWithRetry() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpStop() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNetCtlApRpUnregisterCallback() {
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("XtClSOC1xcU", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweCheckCallbackIpcInt);
LIB_FUNCTION("YALqoY4aeY0", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweClearEventIpcInt);
LIB_FUNCTION("ouyROWhGUbM", "libSceNetBwe", 1, "libSceNetCtl",
sceNetBweFinishInternetConnectionTestIpcInt);
LIB_FUNCTION("G4vltQ0Vs+0", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweGetInfoIpcInt);
LIB_FUNCTION("GqETL5+INhU", "libSceNetBwe", 1, "libSceNetCtl", sceNetBweRegisterCallbackIpcInt);
LIB_FUNCTION("mEUt-phGd5E", "libSceNetBwe", 1, "libSceNetCtl",
sceNetBweSetInternetConnectionTestResultIpcInt);
LIB_FUNCTION("pQLJV5SEAqk", "libSceNetBwe", 1, "libSceNetCtl",
sceNetBweStartInternetConnectionTestBandwidthTestIpcInt);
LIB_FUNCTION("c+aYh130SV0", "libSceNetBwe", 1, "libSceNetCtl",
sceNetBweStartInternetConnectionTestIpcInt);
LIB_FUNCTION("0lViPaTB-R8", "libSceNetBwe", 1, "libSceNetCtl",
sceNetBweUnregisterCallbackIpcInt);
LIB_FUNCTION("Jy1EO5GdlcM", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetInfoV6);
LIB_FUNCTION("H5yARg37U5g", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetResultV6);
LIB_FUNCTION("+lxqIKeU9UY", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlGetStateV6);
LIB_FUNCTION("1NE9OWdBIww", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlRegisterCallbackV6);
LIB_FUNCTION("hIUVeUNxAwc", "libSceNetCtlV6", 1, "libSceNetCtl", sceNetCtlUnregisterCallbackV6);
LIB_FUNCTION("iQw3iQPhvUQ", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlCheckCallback);
LIB_FUNCTION("UF6H6+kjyQs", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlCheckCallbackForLibIpcInt);
LIB_FUNCTION("vv6g8zoanL4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlClearEventForLibIpcInt);
LIB_FUNCTION("8OJ86vFucfo", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlClearEventIpcInt);
LIB_FUNCTION("HCD46HVTyQg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectConfIpcInt);
LIB_FUNCTION("ID+Gq3Ddzbg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectIpcInt);
LIB_FUNCTION("aPpic8K75YA", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlConnectWithRetryIpcInt);
LIB_FUNCTION("9y4IcsJdTCc", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlDisableBandwidthManagementIpcInt);
LIB_FUNCTION("qOefcpoSs0k", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlDisconnectIpcInt);
LIB_FUNCTION("x9bSmRSE+hc", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlEnableBandwidthManagementIpcInt);
LIB_FUNCTION("eCUIlA2t5CE", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetBandwidthInfoIpcInt);
LIB_FUNCTION("2EfjPXVPk3s", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetEtherLinkMode);
LIB_FUNCTION("teuK4QnJTGg", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetIfStat);
LIB_FUNCTION("obuxdTiwkF8", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfo);
LIB_FUNCTION("xstcTqAhTys", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoIpcInt);
LIB_FUNCTION("Jy1EO5GdlcM", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoV6);
LIB_FUNCTION("arAQRFlwqaA", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetInfoV6IpcInt);
LIB_FUNCTION("JO4yuTuMoKI", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetNatInfo);
LIB_FUNCTION("x+cnsAxKSHo", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetNatInfoIpcInt);
LIB_FUNCTION("hhTsdv99azU", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlGetNetEvConfigInfoIpcInt);
LIB_FUNCTION("0cBgduPRR+M", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResult);
LIB_FUNCTION("NEtnusbZyAs", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultIpcInt);
LIB_FUNCTION("H5yARg37U5g", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultV6);
LIB_FUNCTION("vdsTa93atXY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetResultV6IpcInt);
LIB_FUNCTION("wP0Ab2maR1Y", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlGetScanInfoBssidForSsidListScanIpcInt);
LIB_FUNCTION("Wn-+887Lt2s", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetScanInfoBssidIpcInt);
LIB_FUNCTION("FEdkOG1VbQo", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlGetScanInfoByBssidIpcInt);
LIB_FUNCTION("irV8voIAHDw", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlGetScanInfoForSsidListScanIpcInt);
LIB_FUNCTION("L97eAHI0xxs", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlGetScanInfoForSsidScanIpcInt);
LIB_FUNCTION("uBPlr0lbuiI", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetState);
LIB_FUNCTION("JXlI9EZVjf4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetState2IpcInt);
LIB_FUNCTION("gvnJPMkSoAY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateIpcInt);
LIB_FUNCTION("+lxqIKeU9UY", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateV6);
LIB_FUNCTION("O8Fk4w5MWss", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetStateV6IpcInt);
LIB_FUNCTION("BXW9b3R1Nw4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlGetWifiType);
LIB_FUNCTION("gky0+oaNM4k", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlInit);
LIB_FUNCTION("YtAnCkTR0K4", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlIsBandwidthManagementEnabledIpcInt);
LIB_FUNCTION("UJ+Z7Q+4ck0", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallback);
LIB_FUNCTION("WRvDk2syatE", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlRegisterCallbackForLibIpcInt);
LIB_FUNCTION("rqkh2kXvLSw", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallbackIpcInt);
LIB_FUNCTION("1NE9OWdBIww", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlRegisterCallbackV6);
LIB_FUNCTION("ipqlpcIqRsQ", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlRegisterCallbackV6IpcInt);
LIB_FUNCTION("reIsHryCDx4", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlScanIpcInt);
LIB_FUNCTION("LJYiiIS4HB0", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlSetErrorNotificationEnabledIpcInt);
LIB_FUNCTION("DjuqqqV08Nk", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlSetStunWithPaddingFlagIpcInt);
LIB_FUNCTION("Z4wwCFiBELQ", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlTerm);
LIB_FUNCTION("Rqm2OnZMCz0", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlUnregisterCallback);
LIB_FUNCTION("urWaUWkEGZg", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlUnregisterCallbackForLibIpcInt);
LIB_FUNCTION("by9cbB7JGJE", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlUnregisterCallbackIpcInt);
LIB_FUNCTION("hIUVeUNxAwc", "libSceNetCtl", 1, "libSceNetCtl", sceNetCtlUnregisterCallbackV6);
LIB_FUNCTION("Hjxpy28aID8", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlUnregisterCallbackV6IpcInt);
LIB_FUNCTION("1HSvkN9oxO4", "libSceNetCtl", 1, "libSceNetCtl",
sceNetCtlUnsetStunWithPaddingFlagIpcInt);
LIB_FUNCTION("2Ny2lzU3o9w", "libSceNetCtl", 1, "libSceNetCtl", Func_D8DCB6973537A3DC);
LIB_FUNCTION("u5oqtlIP+Fw", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
sceNetCtlCheckCallbackForNpToolkit);
LIB_FUNCTION("saYB0b2ZWtI", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
sceNetCtlClearEventForNpToolkit);
LIB_FUNCTION("wIsKy+TfeLs", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
sceNetCtlRegisterCallbackForNpToolkit);
LIB_FUNCTION("2oUqKR5odGc", "libSceNetCtlForNpToolkit", 1, "libSceNetCtl",
sceNetCtlUnregisterCallbackForNpToolkit);
LIB_FUNCTION("19Ec7WkMFfQ", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApCheckCallback);
LIB_FUNCTION("meFMaDpdsVI", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApClearEvent);
LIB_FUNCTION("hfkLVdXmfnU", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetConnectInfo);
LIB_FUNCTION("LXADzTIzM9I", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetInfo);
LIB_FUNCTION("4jkLJc954+Q", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetResult);
LIB_FUNCTION("AKZOzsb9whc", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApGetState);
LIB_FUNCTION("FdN+edNRtiw", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApInit);
LIB_FUNCTION("pmjobSVHuY0", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApRegisterCallback);
LIB_FUNCTION("r-pOyN6AhsM", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApStop);
LIB_FUNCTION("cv5Y2efOTeg", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApTerm);
LIB_FUNCTION("NpTcFtaQ-0E", "libSceNetCtlAp", 1, "libSceNetCtl", sceNetCtlApUnregisterCallback);
LIB_FUNCTION("R-4a9Yh4tG8", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApAppInitWpaKey);
LIB_FUNCTION("5oLJoOVBbGU", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApAppInitWpaKeyForQa);
LIB_FUNCTION("YtTwZ3pa4aQ", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApAppStartWithRetry);
LIB_FUNCTION("sgWeDrEt24U", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApAppStartWithRetryPid);
LIB_FUNCTION("amqSGH8l--s", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRestart);
LIB_FUNCTION("DufQZgH5ISc", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApRpCheckCallback);
LIB_FUNCTION("qhZbOi+2qLY", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpClearEvent);
LIB_FUNCTION("VQl16Q+qXeY", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetInfo);
LIB_FUNCTION("3pxwYqHzGcw", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetResult);
LIB_FUNCTION("LEn8FGztKWc", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpGetState);
LIB_FUNCTION("ofGsK+xoAaM", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApRpRegisterCallback);
LIB_FUNCTION("mjFgpqNavHg", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStart);
LIB_FUNCTION("HMvaHoZWsn8", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStartConf);
LIB_FUNCTION("9Dxg7XSlr2s", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApRpStartWithRetry);
LIB_FUNCTION("6uvAl4RlEyk", "libSceNetCtlApIpcInt", 1, "libSceNetCtl", sceNetCtlApRpStop);
LIB_FUNCTION("8eyH37Ns8tk", "libSceNetCtlApIpcInt", 1, "libSceNetCtl",
sceNetCtlApRpUnregisterCallback);
};
} // namespace Libraries::NetCtl
+177
View File
@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net_ctl_obj.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::NetCtl {
constexpr int ORBIS_NET_ETHER_ADDR_LEN = 6;
struct OrbisNetEtherAddr {
u8 data[ORBIS_NET_ETHER_ADDR_LEN];
};
constexpr int ORBIS_NET_CTL_SSID_LEN = 32 + 1;
constexpr int ORBIS_NET_CTL_HOSTNAME_LEN = 255 + 1;
constexpr int ORBIS_NET_CTL_AUTH_NAME_LEN = 127 + 1;
constexpr int ORBIS_NET_CTL_IPV4_ADDR_STR_LEN = 16;
union OrbisNetCtlInfo {
u32 device;
OrbisNetEtherAddr ether_addr;
u32 mtu;
u32 link;
OrbisNetEtherAddr bssid;
char ssid[ORBIS_NET_CTL_SSID_LEN];
u32 wifi_security;
u8 rssi_dbm;
uint8_t rssi_percentage;
u8 channel;
u32 ip_config;
char dhcp_hostname[ORBIS_NET_CTL_HOSTNAME_LEN];
char pppoe_auth_name[ORBIS_NET_CTL_AUTH_NAME_LEN];
char ip_address[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
char netmask[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
char default_route[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
char primary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
char secondary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN];
u32 http_proxy_config;
char http_proxy_server[ORBIS_NET_CTL_HOSTNAME_LEN];
u16 http_proxy_port;
};
struct OrbisNetCtlNatInfo {
u32 size;
s32 stun_status;
s32 nat_type;
u32 mapped_addr;
};
static_assert(sizeof(OrbisNetCtlNatInfo) == 0x10);
// GetInfo codes
constexpr int ORBIS_NET_CTL_INFO_DEVICE = 1;
constexpr int ORBIS_NET_CTL_INFO_ETHER_ADDR = 2;
constexpr int ORBIS_NET_CTL_INFO_MTU = 3;
constexpr int ORBIS_NET_CTL_INFO_LINK = 4;
constexpr int ORBIS_NET_CTL_INFO_BSSID = 5;
constexpr int ORBIS_NET_CTL_INFO_SSID = 6;
constexpr int ORBIS_NET_CTL_INFO_WIFI_SECURITY = 7;
constexpr int ORBIS_NET_CTL_INFO_RSSI_DBM = 8;
constexpr int ORBIS_NET_CTL_INFO_RSSI_PERCENTAGE = 9;
constexpr int ORBIS_NET_CTL_INFO_CHANNEL = 10;
constexpr int ORBIS_NET_CTL_INFO_IP_CONFIG = 11;
constexpr int ORBIS_NET_CTL_INFO_DHCP_HOSTNAME = 12;
constexpr int ORBIS_NET_CTL_INFO_PPPOE_AUTH_NAME = 13;
constexpr int ORBIS_NET_CTL_INFO_IP_ADDRESS = 14;
constexpr int ORBIS_NET_CTL_INFO_NETMASK = 15;
constexpr int ORBIS_NET_CTL_INFO_DEFAULT_ROUTE = 16;
constexpr int ORBIS_NET_CTL_INFO_PRIMARY_DNS = 17;
constexpr int ORBIS_NET_CTL_INFO_SECONDARY_DNS = 18;
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_CONFIG = 19;
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_SERVER = 20;
constexpr int ORBIS_NET_CTL_INFO_HTTP_PROXY_PORT = 21;
int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt();
int PS4_SYSV_ABI sceNetBweClearEventIpcInt();
int PS4_SYSV_ABI sceNetBweFinishInternetConnectionTestIpcInt();
int PS4_SYSV_ABI sceNetBweGetInfoIpcInt();
int PS4_SYSV_ABI sceNetBweRegisterCallbackIpcInt();
int PS4_SYSV_ABI sceNetBweSetInternetConnectionTestResultIpcInt();
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestBandwidthTestIpcInt();
int PS4_SYSV_ABI sceNetBweStartInternetConnectionTestIpcInt();
int PS4_SYSV_ABI sceNetBweUnregisterCallbackIpcInt();
int PS4_SYSV_ABI sceNetCtlGetInfoV6();
int PS4_SYSV_ABI sceNetCtlGetResultV6();
int PS4_SYSV_ABI sceNetCtlGetStateV6();
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6();
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6();
int PS4_SYSV_ABI sceNetCtlCheckCallback();
int PS4_SYSV_ABI sceNetCtlCheckCallbackForLibIpcInt();
int PS4_SYSV_ABI sceNetCtlClearEventForLibIpcInt();
int PS4_SYSV_ABI sceNetCtlClearEventIpcInt();
int PS4_SYSV_ABI sceNetCtlConnectConfIpcInt();
int PS4_SYSV_ABI sceNetCtlConnectIpcInt();
int PS4_SYSV_ABI sceNetCtlConnectWithRetryIpcInt();
int PS4_SYSV_ABI sceNetCtlDisableBandwidthManagementIpcInt();
int PS4_SYSV_ABI sceNetCtlDisconnectIpcInt();
int PS4_SYSV_ABI sceNetCtlEnableBandwidthManagementIpcInt();
int PS4_SYSV_ABI sceNetCtlGetBandwidthInfoIpcInt();
int PS4_SYSV_ABI sceNetCtlGetEtherLinkMode();
int PS4_SYSV_ABI sceNetCtlGetIfStat();
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info);
int PS4_SYSV_ABI sceNetCtlGetInfoIpcInt();
int PS4_SYSV_ABI sceNetCtlGetInfoV6IpcInt();
int PS4_SYSV_ABI sceNetCtlGetNatInfo(OrbisNetCtlNatInfo* nat_info);
int PS4_SYSV_ABI sceNetCtlGetNatInfoIpcInt();
int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt();
int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode);
int PS4_SYSV_ABI sceNetCtlGetResultIpcInt();
int PS4_SYSV_ABI sceNetCtlGetResultV6IpcInt();
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt();
int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidIpcInt();
int PS4_SYSV_ABI sceNetCtlGetScanInfoByBssidIpcInt();
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidListScanIpcInt();
int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt();
int PS4_SYSV_ABI sceNetCtlGetState(int* state);
int PS4_SYSV_ABI sceNetCtlGetState2IpcInt();
int PS4_SYSV_ABI sceNetCtlGetStateIpcInt();
int PS4_SYSV_ABI sceNetCtlGetStateV6IpcInt();
int PS4_SYSV_ABI sceNetCtlGetWifiType();
int PS4_SYSV_ABI sceNetCtlInit();
int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt();
int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid);
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForLibIpcInt();
int PS4_SYSV_ABI sceNetCtlRegisterCallbackIpcInt();
int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6IpcInt();
int PS4_SYSV_ABI sceNetCtlScanIpcInt();
int PS4_SYSV_ABI sceNetCtlSetErrorNotificationEnabledIpcInt();
int PS4_SYSV_ABI sceNetCtlSetStunWithPaddingFlagIpcInt();
int PS4_SYSV_ABI sceNetCtlTerm();
int PS4_SYSV_ABI sceNetCtlUnregisterCallback();
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForLibIpcInt();
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackIpcInt();
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6IpcInt();
int PS4_SYSV_ABI sceNetCtlUnsetStunWithPaddingFlagIpcInt();
int PS4_SYSV_ABI Func_D8DCB6973537A3DC();
int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit();
int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit();
int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func,
void* arg, int* cid);
int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForNpToolkit();
int PS4_SYSV_ABI sceNetCtlApCheckCallback();
int PS4_SYSV_ABI sceNetCtlApClearEvent();
int PS4_SYSV_ABI sceNetCtlApGetConnectInfo();
int PS4_SYSV_ABI sceNetCtlApGetInfo();
int PS4_SYSV_ABI sceNetCtlApGetResult();
int PS4_SYSV_ABI sceNetCtlApGetState();
int PS4_SYSV_ABI sceNetCtlApInit();
int PS4_SYSV_ABI sceNetCtlApRegisterCallback();
int PS4_SYSV_ABI sceNetCtlApStop();
int PS4_SYSV_ABI sceNetCtlApTerm();
int PS4_SYSV_ABI sceNetCtlApUnregisterCallback();
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKey();
int PS4_SYSV_ABI sceNetCtlApAppInitWpaKeyForQa();
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetry();
int PS4_SYSV_ABI sceNetCtlApAppStartWithRetryPid();
int PS4_SYSV_ABI sceNetCtlApRestart();
int PS4_SYSV_ABI sceNetCtlApRpCheckCallback();
int PS4_SYSV_ABI sceNetCtlApRpClearEvent();
int PS4_SYSV_ABI sceNetCtlApRpGetInfo();
int PS4_SYSV_ABI sceNetCtlApRpGetResult();
int PS4_SYSV_ABI sceNetCtlApRpGetState();
int PS4_SYSV_ABI sceNetCtlApRpRegisterCallback();
int PS4_SYSV_ABI sceNetCtlApRpStart();
int PS4_SYSV_ABI sceNetCtlApRpStartConf();
int PS4_SYSV_ABI sceNetCtlApRpStartWithRetry();
int PS4_SYSV_ABI sceNetCtlApRpStop();
int PS4_SYSV_ABI sceNetCtlApRpUnregisterCallback();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::NetCtl
@@ -0,0 +1,996 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <limits>
#include <thread>
#include <unordered_map>
#include <vector>
#ifdef __linux__
#include <sys/eventfd.h>
#endif
// clang-format off
#include "sockets.h"
// clang-format on
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/network/net_util.h"
#include "net.h"
#include "net_error.h"
namespace Libraries::Net {
namespace {
constexpr u16 kSignalingVPortNbo = 0xffff;
constexpr size_t kVPortHeaderSize = 4;
constexpr size_t kMaxUdpPayload = 65507;
constexpr size_t kMaxP2PPayload = kMaxUdpPayload - kVPortHeaderSize;
#ifdef _WIN32
constexpr net_socket kInvalidSocket = INVALID_SOCKET;
#else
constexpr net_socket kInvalidSocket = -1;
#endif
enum class InternalChannel {
Signaling,
Control,
Matching2,
};
struct QueuedPacket {
u32 source_addr{};
u16 source_port{};
std::vector<u8> payload;
};
int FailWith(int error) {
*Libraries::Kernel::__Error() = error;
return -1;
}
bool IsValidSocket(net_socket socket) {
return socket != kInvalidSocket;
}
int LastSocketError() {
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
bool IsRetryableSocketError(int error) {
#ifdef _WIN32
return error == WSAEWOULDBLOCK || error == WSAEINTR;
#else
return error == EAGAIN || error == EWOULDBLOCK || error == EINTR;
#endif
}
void CloseSocket(net_socket& socket) {
if (!IsValidSocket(socket)) {
return;
}
#ifdef _WIN32
::closesocket(socket);
#else
::close(socket);
#endif
socket = kInvalidSocket;
}
bool SetSocketNonBlocking(net_socket socket) {
#ifdef _WIN32
u_long enabled = 1;
return ::ioctlsocket(socket, FIONBIO, &enabled) == 0;
#else
int enabled = 1;
return ::ioctl(socket, FIONBIO, &enabled) == 0;
#endif
}
#ifdef _WIN32
bool CreateReadinessPair(net_socket* read_socket, net_socket* signal_socket) {
*read_socket = kInvalidSocket;
*signal_socket = kInvalidSocket;
net_socket reader = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!IsValidSocket(reader)) {
return false;
}
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = 0;
if (::bind(reader, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) < 0) {
CloseSocket(reader);
return false;
}
socklen_t address_len = sizeof(address);
if (::getsockname(reader, reinterpret_cast<sockaddr*>(&address), &address_len) < 0) {
CloseSocket(reader);
return false;
}
net_socket signaler = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!IsValidSocket(signaler) ||
::connect(signaler, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) < 0 ||
!SetSocketNonBlocking(reader) || !SetSocketNonBlocking(signaler)) {
CloseSocket(signaler);
CloseSocket(reader);
return false;
}
*read_socket = reader;
*signal_socket = signaler;
return true;
}
#endif
class P2PTransport {
public:
static P2PTransport& Instance() {
static P2PTransport transport;
return transport;
}
~P2PTransport() {
Stop();
}
bool Start() {
#if !defined(__linux__) && !defined(_WIN32)
LOG_ERROR(Lib_Net, "P2P transport is currently supported on Linux and Windows");
return false;
#else
std::lock_guard lock(start_mutex);
if (running.load(std::memory_order_acquire)) {
return true;
}
host_socket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!IsValidSocket(host_socket)) {
LOG_ERROR(Lib_Net, "P2P transport socket() failed error={}", LastSocketError());
return false;
}
int one = 1;
(void)::setsockopt(host_socket, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&one), sizeof(one));
if (!SetSocketNonBlocking(host_socket)) {
LOG_ERROR(Lib_Net, "P2P transport failed to make socket nonblocking error={}",
LastSocketError());
CloseSocket(host_socket);
return false;
}
u16 requested_port = 0;
if (const char* env = std::getenv("SHADPS4_P2P_PORT"); env != nullptr && *env != '\0') {
char* end = nullptr;
const long parsed = std::strtol(env, &end, 10);
if (end == env || *end != '\0' || parsed < 1 || parsed > 65535) {
LOG_ERROR(Lib_Net, "Invalid SHADPS4_P2P_PORT='{}'", env);
CloseSocket(host_socket);
return false;
}
requested_port = static_cast<u16>(parsed);
}
sockaddr_in bind_addr{};
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind_addr.sin_port = htons(requested_port);
if (::bind(host_socket, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
LOG_ERROR(Lib_Net, "P2P transport bind(port={}) failed error={}", requested_port,
LastSocketError());
CloseSocket(host_socket);
return false;
}
sockaddr_in actual{};
socklen_t actual_len = sizeof(actual);
if (::getsockname(host_socket, reinterpret_cast<sockaddr*>(&actual), &actual_len) < 0) {
LOG_ERROR(Lib_Net, "P2P transport getsockname() failed error={}", LastSocketError());
CloseSocket(host_socket);
return false;
}
physical_port = ntohs(actual.sin_port);
running.store(true, std::memory_order_release);
receive_thread = std::thread(&P2PTransport::ReceiveMain, this);
LOG_INFO(Lib_Net,
"P2P transport ready: UDP physical_port={} (set SHADPS4_P2P_PORT to pin it)",
physical_port);
return true;
#endif
}
void Stop() {
#if defined(__linux__) || defined(_WIN32)
std::lock_guard lock(start_mutex);
if (!running.exchange(false, std::memory_order_acq_rel)) {
return;
}
if (receive_thread.joinable()) {
receive_thread.join();
}
CloseSocket(host_socket);
physical_port = 0;
#endif
}
bool IsReady() const {
return running.load(std::memory_order_acquire);
}
u16 PhysicalPort() const {
return physical_port;
}
bool RegisterSocket(P2PSocket* socket, u16 requested_vport_nbo, u16* actual_vport_nbo) {
if (socket == nullptr || actual_vport_nbo == nullptr) {
return false;
}
std::lock_guard lock(registry_mutex);
u16 vport = requested_vport_nbo;
// vport 0 acts as an ephemeral request in this PoC.
if (vport == 0) {
for (u32 attempt = 0; attempt < 35000; ++attempt) {
if (next_ephemeral_vport >= 65535) {
next_ephemeral_vport = 30000;
}
const u16 candidate_host = next_ephemeral_vport++;
const u16 candidate_nbo = htons(candidate_host);
if (candidate_nbo == kSignalingVPortNbo || game_sockets.contains(candidate_nbo)) {
continue;
}
vport = candidate_nbo;
break;
}
}
if (vport == 0 || vport == kSignalingVPortNbo || game_sockets.contains(vport)) {
return false;
}
game_sockets.emplace(vport, socket);
*actual_vport_nbo = vport;
return true;
}
void UnregisterSocket(P2PSocket* socket, u16 vport_nbo) {
std::lock_guard lock(registry_mutex);
const auto it = game_sockets.find(vport_nbo);
if (it != game_sockets.end() && it->second == socket) {
game_sockets.erase(it);
}
}
int SendFrame(u16 source_vport_nbo, u16 dest_vport_nbo, const void* data, u32 len,
u32 dest_addr_nbo, u16 dest_port_nbo) {
#if !defined(__linux__) && !defined(_WIN32)
return -1;
#else
if (!Start() || !IsValidSocket(host_socket) || (data == nullptr && len != 0) ||
len > kMaxP2PPayload || dest_addr_nbo == 0 || dest_port_nbo == 0) {
return -1;
}
std::vector<u8> framed(kVPortHeaderSize + len);
std::memcpy(framed.data(), &source_vport_nbo, sizeof(source_vport_nbo));
std::memcpy(framed.data() + sizeof(source_vport_nbo), &dest_vport_nbo,
sizeof(dest_vport_nbo));
if (len != 0) {
std::memcpy(framed.data() + kVPortHeaderSize, data, len);
}
sockaddr_in dest{};
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = dest_addr_nbo;
dest.sin_port = dest_port_nbo;
const auto rc = ::sendto(host_socket, reinterpret_cast<const char*>(framed.data()),
static_cast<int>(framed.size()), 0,
reinterpret_cast<const sockaddr*>(&dest), sizeof(dest));
if (rc < 0) {
LOG_DEBUG(Lib_Net, "P2P sendto failed error={} dst={:#x}:{}", LastSocketError(),
dest_addr_nbo, ntohs(dest_port_nbo));
return -1;
}
if (static_cast<size_t>(rc) != framed.size()) {
LOG_WARNING(Lib_Net, "P2P short UDP send: {} of {} bytes", rc, framed.size());
return -1;
}
return static_cast<int>(len);
#endif
}
int SendInternal(const void* data, u32 len, u32 dest_addr_nbo, u16 dest_port_nbo) {
return SendFrame(kSignalingVPortNbo, kSignalingVPortNbo, data, len, dest_addr_nbo,
dest_port_nbo);
}
int ReceiveInternal(InternalChannel channel, void* buf, u32 len, u32* from_addr,
u16* from_port) {
std::lock_guard lock(internal_mutex);
auto& queue = QueueFor(channel);
if (queue.empty()) {
return -1;
}
QueuedPacket packet = std::move(queue.front());
queue.pop_front();
const u32 copy_len = static_cast<u32>(std::min<size_t>(len, packet.payload.size()));
if (copy_len != 0 && buf != nullptr) {
std::memcpy(buf, packet.payload.data(), copy_len);
}
if (from_addr != nullptr) {
*from_addr = packet.source_addr;
}
if (from_port != nullptr) {
*from_port = packet.source_port;
}
return static_cast<int>(copy_len);
}
private:
P2PTransport() = default;
std::deque<QueuedPacket>& QueueFor(InternalChannel channel) {
switch (channel) {
case InternalChannel::Signaling:
return signaling_queue;
case InternalChannel::Control:
return control_queue;
case InternalChannel::Matching2:
return matching2_queue;
}
return signaling_queue;
}
static InternalChannel ClassifyInternal(const u8* payload, size_t size) {
if (size >= 5 && std::memcmp(payload, "SHAD", 4) == 0) {
if (payload[4] == 0x21) {
return InternalChannel::Matching2;
}
if (payload[4] == 0x10) {
return InternalChannel::Control;
}
}
// STUN echo (6 bytes) and generic signaling 0x05/0x06/0x07 land here.
return InternalChannel::Signaling;
}
void PushInternal(InternalChannel channel, u32 from_addr, u16 from_port, const u8* payload,
size_t size) {
QueuedPacket packet{};
packet.source_addr = from_addr;
packet.source_port = from_port;
packet.payload.assign(payload, payload + size);
std::lock_guard lock(internal_mutex);
QueueFor(channel).push_back(std::move(packet));
}
void ReceiveMain() {
#if defined(__linux__) || defined(_WIN32)
std::vector<u8> buffer(65536);
while (running.load(std::memory_order_acquire)) {
sockaddr_in from{};
socklen_t from_len = sizeof(from);
#ifdef _WIN32
constexpr int receive_flags = 0;
#else
constexpr int receive_flags = MSG_DONTWAIT;
#endif
const auto rc = ::recvfrom(host_socket, reinterpret_cast<char*>(buffer.data()),
static_cast<int>(buffer.size()), receive_flags,
reinterpret_cast<sockaddr*>(&from), &from_len);
if (rc < 0) {
const int error = LastSocketError();
if (IsRetryableSocketError(error)) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
continue;
}
LOG_WARNING(Lib_Net, "P2P recvfrom failed error={}", error);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
if (rc < static_cast<decltype(rc)>(kVPortHeaderSize)) {
LOG_DEBUG(Lib_Net, "P2P dropping too-short UDP frame size={}", rc);
continue;
}
u16 source_vport_nbo = 0;
u16 dest_vport_nbo = 0;
std::memcpy(&source_vport_nbo, buffer.data(), sizeof(source_vport_nbo));
std::memcpy(&dest_vport_nbo, buffer.data() + sizeof(source_vport_nbo),
sizeof(dest_vport_nbo));
const u8* payload = buffer.data() + kVPortHeaderSize;
const size_t payload_size = static_cast<size_t>(rc) - kVPortHeaderSize;
if (dest_vport_nbo == kSignalingVPortNbo) {
PushInternal(ClassifyInternal(payload, payload_size), from.sin_addr.s_addr,
from.sin_port, payload, payload_size);
continue;
}
std::lock_guard lock(registry_mutex);
const auto it = game_sockets.find(dest_vport_nbo);
if (it == game_sockets.end() || it->second == nullptr) {
LOG_TRACE(Lib_Net, "P2P RX no socket for dst_vport={}", ntohs(dest_vport_nbo));
continue;
}
P2PDatagram packet{};
packet.source_addr = from.sin_addr.s_addr;
packet.source_port = from.sin_port;
packet.source_vport = source_vport_nbo;
packet.payload.assign(payload, payload + payload_size);
it->second->EnqueuePacket(std::move(packet));
}
#endif
}
mutable std::mutex start_mutex;
std::atomic<bool> running{false};
std::thread receive_thread;
net_socket host_socket{kInvalidSocket};
u16 physical_port{0};
std::mutex registry_mutex;
std::unordered_map<u16, P2PSocket*> game_sockets;
u16 next_ephemeral_vport{30000};
std::mutex internal_mutex;
std::deque<QueuedPacket> signaling_queue;
std::deque<QueuedPacket> control_queue;
std::deque<QueuedPacket> matching2_queue;
};
int ReadIntegerOption(const void* optval, u32 optlen, int* out) {
if (optval == nullptr || out == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (optlen < sizeof(int)) {
return FailWith(ORBIS_NET_EINVAL);
}
std::memcpy(out, optval, sizeof(int));
return 0;
}
int WriteIntegerOption(void* optval, u32* optlen, int value) {
if (optval == nullptr || optlen == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (*optlen < sizeof(int)) {
return FailWith(ORBIS_NET_EINVAL);
}
std::memcpy(optval, &value, sizeof(int));
*optlen = sizeof(int);
return 0;
}
} // namespace
P2PSocket::P2PSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {
#ifdef _WIN32
if (!CreateReadinessPair(&readiness_fd, &readiness_signal_fd)) {
LOG_ERROR(Lib_Net, "P2P readiness socket pair failed error={}", LastSocketError());
valid = false;
}
#elif defined(__linux__)
readiness_fd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (readiness_fd < 0) {
LOG_ERROR(Lib_Net, "P2P eventfd() failed errno={}", errno);
valid = false;
}
#endif
}
P2PSocket::~P2PSocket() {
Close();
}
int P2PSocket::Close() {
std::lock_guard lock(m_mutex);
if (!valid) {
return 0;
}
if (bound) {
P2PTransport::Instance().UnregisterSocket(this, local_addr.sin_vport);
bound = false;
}
{
std::lock_guard receive_lock(receive_mutex);
valid = false;
}
connected = false;
receive_cv.notify_all();
#ifdef _WIN32
CloseSocket(readiness_signal_fd);
CloseSocket(readiness_fd);
#elif defined(__linux__)
if (readiness_fd >= 0) {
::close(readiness_fd);
readiness_fd = -1;
}
#endif
return 0;
}
int P2PSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
if (level != ORBIS_NET_SOL_SOCKET) {
LOG_WARNING(Lib_Net, "P2P setsockopt ignoring non-SOL_SOCKET level={} opt={}", level,
optname);
return 0;
}
int value = 0;
switch (optname) {
case ORBIS_NET_SO_NBIO:
case ORBIS_NET_SO_USECRYPTO:
case ORBIS_NET_SO_USESIGNATURE:
case ORBIS_NET_SO_REUSEADDR:
case ORBIS_NET_SO_REUSEPORT:
case ORBIS_NET_SO_KEEPALIVE:
case ORBIS_NET_SO_BROADCAST:
case ORBIS_NET_SO_ONESBCAST:
case ORBIS_NET_SO_SNDBUF:
case ORBIS_NET_SO_RCVBUF:
case ORBIS_NET_SO_PRIORITY:
if (ReadIntegerOption(optval, optlen, &value) < 0) {
return -1;
}
integer_socket_options[optname] = value;
if (optname == ORBIS_NET_SO_NBIO) {
std::lock_guard receive_lock(receive_mutex);
sockopt_so_nbio = value;
} else if (optname == ORBIS_NET_SO_USECRYPTO) {
sockopt_so_usecrypto = value;
} else if (optname == ORBIS_NET_SO_USESIGNATURE) {
sockopt_so_usesignature = value;
}
return 0;
default:
// For the first emulator-to-emulator PoC, remember that an option was requested
// but do not fail the title merely because the Sony-specific behavior is absent.
LOG_WARNING(Lib_Net, "P2P setsockopt opt={:#x} accepted as no-op", optname);
return 0;
}
}
int P2PSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
if (level != ORBIS_NET_SOL_SOCKET) {
return FailWith(ORBIS_NET_ENOPROTOOPT);
}
switch (optname) {
case ORBIS_NET_SO_NBIO:
return WriteIntegerOption(optval, optlen, sockopt_so_nbio);
case ORBIS_NET_SO_USECRYPTO:
return WriteIntegerOption(optval, optlen, sockopt_so_usecrypto);
case ORBIS_NET_SO_USESIGNATURE:
return WriteIntegerOption(optval, optlen, sockopt_so_usesignature);
case ORBIS_NET_SO_TYPE:
return WriteIntegerOption(optval, optlen, socket_type);
case ORBIS_NET_SO_ERROR:
case ORBIS_NET_SO_ERROR_EX:
return WriteIntegerOption(optval, optlen, 0);
default:
if (const auto it = integer_socket_options.find(optname);
it != integer_socket_options.end()) {
return WriteIntegerOption(optval, optlen, it->second);
}
return FailWith(ORBIS_NET_ENOPROTOOPT);
}
}
int P2PSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
return FailWith(ORBIS_NET_EOPNOTSUPP);
}
if (addr == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (addrlen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
const auto* in = reinterpret_cast<const OrbisNetSockaddrIn*>(addr);
if (in->sin_family != ORBIS_NET_AF_INET) {
return FailWith(ORBIS_NET_EAFNOSUPPORT);
}
if (!EnsureP2PTransport()) {
return FailWith(ORBIS_NET_ENETDOWN);
}
std::lock_guard lock(m_mutex);
if (!valid) {
return FailWith(ORBIS_NET_EBADF);
}
if (bound) {
return FailWith(ORBIS_NET_EINVAL);
}
u16 actual_vport = 0;
if (!P2PTransport::Instance().RegisterSocket(this, in->sin_vport, &actual_vport)) {
return FailWith(ORBIS_NET_EADDRINUSE);
}
local_addr = *in;
local_addr.sin_len = sizeof(OrbisNetSockaddrIn);
local_addr.sin_family = ORBIS_NET_AF_INET;
local_addr.sin_port = htons(P2PTransport::Instance().PhysicalPort());
local_addr.sin_vport = actual_vport;
bound = true;
LOG_INFO(Lib_Net, "P2P bind: physical_port={} virtual_port={}",
P2PTransport::Instance().PhysicalPort(), ntohs(local_addr.sin_vport));
return 0;
}
int P2PSocket::Listen(int backlog) {
LOG_WARNING(Lib_Net, "P2P STREAM listen not implemented (backlog={})", backlog);
return FailWith(ORBIS_NET_EOPNOTSUPP);
}
int P2PSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
if (msg == nullptr || msg->msg_iovlen < 0) {
return FailWith(ORBIS_NET_EINVAL);
}
size_t total = 0;
for (int i = 0; i < msg->msg_iovlen; ++i) {
total += static_cast<size_t>(msg->msg_iov[i].iov_len);
if (total > kMaxP2PPayload || total > std::numeric_limits<u32>::max()) {
return FailWith(ORBIS_NET_EMSGSIZE);
}
}
std::vector<u8> payload(total);
size_t offset = 0;
for (int i = 0; i < msg->msg_iovlen; ++i) {
const auto len = static_cast<size_t>(msg->msg_iov[i].iov_len);
if (len != 0) {
std::memcpy(payload.data() + offset, msg->msg_iov[i].iov_base, len);
}
offset += len;
}
return SendPacket(payload.data(), static_cast<u32>(payload.size()), flags,
reinterpret_cast<const OrbisNetSockaddr*>(msg->msg_name), msg->msg_namelen);
}
int P2PSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
return FailWith(ORBIS_NET_EOPNOTSUPP);
}
if (msg == nullptr && len != 0) {
return FailWith(ORBIS_NET_EFAULT);
}
if (len > kMaxP2PPayload) {
return FailWith(ORBIS_NET_EMSGSIZE);
}
if (!EnsureP2PTransport()) {
return FailWith(ORBIS_NET_ENETDOWN);
}
std::lock_guard lock(m_mutex);
if (!valid) {
return FailWith(ORBIS_NET_EBADF);
}
// UDP permits implicit local binding. Give an unbound guest socket an ephemeral vport.
if (!bound) {
u16 actual_vport = 0;
if (!P2PTransport::Instance().RegisterSocket(this, 0, &actual_vport)) {
return FailWith(ORBIS_NET_EADDRINUSE);
}
local_addr = {};
local_addr.sin_len = sizeof(local_addr);
local_addr.sin_family = ORBIS_NET_AF_INET;
local_addr.sin_port = htons(P2PTransport::Instance().PhysicalPort());
local_addr.sin_vport = actual_vport;
bound = true;
}
OrbisNetSockaddrIn target{};
if (to != nullptr) {
if (tolen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
target = *reinterpret_cast<const OrbisNetSockaddrIn*>(to);
if (target.sin_family != ORBIS_NET_AF_INET) {
return FailWith(ORBIS_NET_EAFNOSUPPORT);
}
} else {
if (!connected) {
return FailWith(ORBIS_NET_ENOTCONN);
}
target = peer_addr;
}
const int rc = P2PTransport::Instance().SendFrame(local_addr.sin_vport, target.sin_vport, msg,
len, target.sin_addr, target.sin_port);
if (rc < 0) {
return FailWith(ORBIS_NET_EHOSTUNREACH);
}
LOG_TRACE(Lib_Net, "P2P TX {} bytes vport {} -> {} dst={:#x}:{}", len,
ntohs(local_addr.sin_vport), ntohs(target.sin_vport), target.sin_addr,
ntohs(target.sin_port));
return rc;
}
int P2PSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
if (msg == nullptr || msg->msg_iovlen < 0) {
return FailWith(ORBIS_NET_EINVAL);
}
size_t capacity = 0;
for (int i = 0; i < msg->msg_iovlen; ++i) {
capacity += static_cast<size_t>(msg->msg_iov[i].iov_len);
if (capacity > std::numeric_limits<u32>::max()) {
return FailWith(ORBIS_NET_EMSGSIZE);
}
}
std::vector<u8> payload(capacity);
u32 name_len = msg->msg_namelen;
const int rc = ReceivePacket(payload.data(), static_cast<u32>(payload.size()), flags,
reinterpret_cast<OrbisNetSockaddr*>(msg->msg_name),
msg->msg_name != nullptr ? &name_len : nullptr);
if (rc < 0) {
return rc;
}
msg->msg_namelen = name_len;
msg->msg_flags = 0;
size_t remaining = static_cast<size_t>(rc);
size_t offset = 0;
for (int i = 0; i < msg->msg_iovlen && remaining != 0; ++i) {
const size_t amount =
std::min<size_t>(remaining, static_cast<size_t>(msg->msg_iov[i].iov_len));
if (amount != 0) {
std::memcpy(msg->msg_iov[i].iov_base, payload.data() + offset, amount);
}
offset += amount;
remaining -= amount;
}
return rc;
}
int P2PSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
if (buf == nullptr && len != 0) {
return FailWith(ORBIS_NET_EFAULT);
}
std::unique_lock lock(receive_mutex);
while (receive_queue.empty() && valid) {
if (sockopt_so_nbio != 0 || (flags & ORBIS_NET_MSG_DONTWAIT) != 0) {
return FailWith(ORBIS_NET_EWOULDBLOCK);
}
receive_cv.wait(lock);
}
if (!valid) {
return FailWith(ORBIS_NET_EBADF);
}
const P2PDatagram& packet = receive_queue.front();
const u32 amount = static_cast<u32>(std::min<size_t>(len, packet.payload.size()));
if (amount != 0) {
std::memcpy(buf, packet.payload.data(), amount);
}
if (from != nullptr && fromlen != nullptr) {
if (*fromlen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
auto* out = reinterpret_cast<OrbisNetSockaddrIn*>(from);
*out = {};
out->sin_len = sizeof(OrbisNetSockaddrIn);
out->sin_family = ORBIS_NET_AF_INET;
out->sin_addr = packet.source_addr;
out->sin_port = packet.source_port;
out->sin_vport = packet.source_vport;
*fromlen = sizeof(OrbisNetSockaddrIn);
}
const bool peek = (flags & ORBIS_NET_MSG_PEEK) != 0;
const bool peek_len =
(flags & ORBIS_NET_MSG_PEEKLEN) == static_cast<int>(ORBIS_NET_MSG_PEEKLEN);
const int result =
peek_len ? static_cast<int>(packet.payload.size()) : static_cast<int>(amount);
if (!peek) {
receive_queue.pop_front();
#ifdef _WIN32
if (receive_queue.empty() && IsValidSocket(readiness_fd)) {
char signal = 0;
(void)::recv(readiness_fd, &signal, sizeof(signal), 0);
}
#elif defined(__linux__)
if (receive_queue.empty() && readiness_fd >= 0) {
u64 event_count = 0;
(void)::read(readiness_fd, &event_count, sizeof(event_count));
}
#endif
}
return result;
}
SocketPtr P2PSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
LOG_WARNING(Lib_Net, "P2P STREAM accept not implemented");
*Libraries::Kernel::__Error() = ORBIS_NET_EOPNOTSUPP;
return nullptr;
}
int P2PSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
if (socket_type != ORBIS_NET_SOCK_DGRAM_P2P) {
return FailWith(ORBIS_NET_EOPNOTSUPP);
}
if (addr == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (namelen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
const auto* in = reinterpret_cast<const OrbisNetSockaddrIn*>(addr);
if (in->sin_family != ORBIS_NET_AF_INET) {
return FailWith(ORBIS_NET_EAFNOSUPPORT);
}
if (!EnsureP2PTransport()) {
return FailWith(ORBIS_NET_ENETDOWN);
}
std::lock_guard lock(m_mutex);
peer_addr = *in;
connected = true;
return 0;
}
int P2PSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
if (name == nullptr || namelen == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (*namelen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
auto out = local_addr;
out.sin_len = sizeof(out);
out.sin_family = ORBIS_NET_AF_INET;
if (!bound && EnsureP2PTransport()) {
out.sin_port = htons(GetP2PConfiguredPort());
}
std::memcpy(name, &out, sizeof(out));
*namelen = sizeof(out);
return 0;
}
int P2PSocket::GetPeerName(OrbisNetSockaddr* addr, u32* namelen) {
if (!connected) {
return FailWith(ORBIS_NET_ENOTCONN);
}
if (addr == nullptr || namelen == nullptr) {
return FailWith(ORBIS_NET_EFAULT);
}
if (*namelen < sizeof(OrbisNetSockaddrIn)) {
return FailWith(ORBIS_NET_EINVAL);
}
std::memcpy(addr, &peer_addr, sizeof(peer_addr));
*namelen = sizeof(peer_addr);
return 0;
}
int P2PSocket::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
LOG_DEBUG(Lib_Net, "P2P fstat called");
return 0;
}
void P2PSocket::EnqueuePacket(P2PDatagram packet) {
std::lock_guard lock(receive_mutex);
if (!valid) {
return;
}
const bool was_empty = receive_queue.empty();
LOG_TRACE(Lib_Net, "P2P RX {} bytes vport {} <- {} src={:#x}:{}", packet.payload.size(),
ntohs(local_addr.sin_vport), ntohs(packet.source_vport), packet.source_addr,
ntohs(packet.source_port));
receive_queue.push_back(std::move(packet));
#ifdef _WIN32
if (was_empty && IsValidSocket(readiness_signal_fd)) {
constexpr char signal = 1;
if (::send(readiness_signal_fd, &signal, sizeof(signal), 0) < 0) {
LOG_WARNING(Lib_Net, "P2P readiness signal failed error={}", LastSocketError());
}
}
#elif defined(__linux__)
if (was_empty && readiness_fd >= 0) {
const u64 one = 1;
(void)::write(readiness_fd, &one, sizeof(one));
}
#endif
receive_cv.notify_all();
}
u16 GetP2PConfiguredPort() {
return P2PTransport::Instance().PhysicalPort();
}
u32 GetP2PAdvertisedAddr() {
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
return netinfo->GetExternalIp();
}
bool EnsureP2PTransport() {
return P2PTransport::Instance().Start();
}
bool P2PTransportIsReady() {
return P2PTransport::Instance().IsReady();
}
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
}
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Signaling, buf, len, from_addr,
from_port);
}
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
}
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Control, buf, len, from_addr,
from_port);
}
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return P2PTransport::Instance().SendInternal(data, len, dest_addr, dest_port);
}
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return P2PTransport::Instance().ReceiveInternal(InternalChannel::Matching2, buf, len, from_addr,
from_port);
}
} // namespace Libraries::Net
@@ -0,0 +1,712 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include <common/assert.h>
#include "common/error.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "net.h"
#ifndef _WIN32
#include <sys/stat.h>
#endif
#include "net_error.h"
#include "sockets.h"
namespace Libraries::Net {
#ifdef _WIN32
#define ERROR_CASE(errname) \
case (WSA##errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#else
#define ERROR_CASE(errname) \
case (errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#endif
static int ConvertReturnErrorCode(int retval) {
if (retval < 0) {
#ifdef _WIN32
switch (WSAGetLastError()) {
#else
switch (errno) {
#endif
#ifndef _WIN32 // These errorcodes don't exist in WinSock
ERROR_CASE(EPERM)
ERROR_CASE(ENOENT)
// ERROR_CASE(ESRCH)
// ERROR_CASE(EIO)
// ERROR_CASE(ENXIO)
// ERROR_CASE(E2BIG)
// ERROR_CASE(ENOEXEC)
// ERROR_CASE(EDEADLK)
ERROR_CASE(ENOMEM)
// ERROR_CASE(ECHILD)
// ERROR_CASE(EBUSY)
ERROR_CASE(EEXIST)
// ERROR_CASE(EXDEV)
ERROR_CASE(ENODEV)
// ERROR_CASE(ENOTDIR)
// ERROR_CASE(EISDIR)
ERROR_CASE(ENFILE)
// ERROR_CASE(ENOTTY)
// ERROR_CASE(ETXTBSY)
// ERROR_CASE(EFBIG)
ERROR_CASE(ENOSPC)
// ERROR_CASE(ESPIPE)
// ERROR_CASE(EROFS)
// ERROR_CASE(EMLINK)
ERROR_CASE(EPIPE)
// ERROR_CASE(EDOM)
// ERROR_CASE(ERANGE)
// ERROR_CASE(ENOLCK)
// ERROR_CASE(ENOSYS)
// ERROR_CASE(EIDRM)
// ERROR_CASE(EOVERFLOW)
// ERROR_CASE(EILSEQ)
// ERROR_CASE(ENOTSUP)
ERROR_CASE(ECANCELED)
// ERROR_CASE(EBADMSG)
ERROR_CASE(ENODATA)
// ERROR_CASE(ENOSR)
// ERROR_CASE(ENOSTR)
// ERROR_CASE(ETIME)
#endif
ERROR_CASE(EINTR)
ERROR_CASE(EBADF)
ERROR_CASE(EACCES)
ERROR_CASE(EFAULT)
ERROR_CASE(EINVAL)
ERROR_CASE(EMFILE)
ERROR_CASE(EWOULDBLOCK)
ERROR_CASE(EINPROGRESS)
ERROR_CASE(EALREADY)
ERROR_CASE(ENOTSOCK)
ERROR_CASE(EDESTADDRREQ)
ERROR_CASE(EMSGSIZE)
ERROR_CASE(EPROTOTYPE)
ERROR_CASE(ENOPROTOOPT)
ERROR_CASE(EPROTONOSUPPORT)
#if defined(__APPLE__) || defined(_WIN32)
ERROR_CASE(EOPNOTSUPP)
#endif
ERROR_CASE(EAFNOSUPPORT)
ERROR_CASE(EADDRINUSE)
ERROR_CASE(EADDRNOTAVAIL)
ERROR_CASE(ENETDOWN)
ERROR_CASE(ENETUNREACH)
ERROR_CASE(ENETRESET)
ERROR_CASE(ECONNABORTED)
ERROR_CASE(ECONNRESET)
ERROR_CASE(ENOBUFS)
ERROR_CASE(EISCONN)
ERROR_CASE(ENOTCONN)
ERROR_CASE(ETIMEDOUT)
ERROR_CASE(ECONNREFUSED)
ERROR_CASE(ELOOP)
ERROR_CASE(ENAMETOOLONG)
ERROR_CASE(EHOSTUNREACH)
ERROR_CASE(ENOTEMPTY)
}
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
return -1;
}
// if it is 0 or positive return it as it is
return retval;
}
static int ConvertLevels(int level) {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return SOL_SOCKET;
case ORBIS_NET_IPPROTO_IP:
return IPPROTO_IP;
case ORBIS_NET_IPPROTO_TCP:
return IPPROTO_TCP;
case ORBIS_NET_IPPROTO_IPV6:
return IPPROTO_IPV6;
}
return -1;
}
static void convertOrbisNetSockaddrToPosix(const OrbisNetSockaddr* src, sockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(sockaddr));
const OrbisNetSockaddrIn* src_in = (const OrbisNetSockaddrIn*)src;
sockaddr_in* dst_in = (sockaddr_in*)dst;
dst_in->sin_family = src_in->sin_family;
dst_in->sin_port = src_in->sin_port;
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
}
static void convertPosixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(OrbisNetSockaddr));
OrbisNetSockaddrIn* dst_in = (OrbisNetSockaddrIn*)dst;
sockaddr_in* src_in = (sockaddr_in*)src;
dst_in->sin_family = static_cast<unsigned char>(src_in->sin_family);
dst_in->sin_port = src_in->sin_port;
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
}
bool PosixSocket::IsValid() const {
#ifdef _WIN32
return sock != INVALID_SOCKET;
#else
return sock != -1;
#endif
}
int PosixSocket::Close() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
auto out = closesocket(sock);
#else
auto out = ::close(sock);
#endif
return ConvertReturnErrorCode(out);
}
int PosixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
convertOrbisNetSockaddrToPosix(addr, &addr2);
return ConvertReturnErrorCode(::bind(sock, &addr2, sizeof(sockaddr_in)));
}
int PosixSocket::Listen(int backlog) {
std::scoped_lock lock{m_mutex};
return ConvertReturnErrorCode(::listen(sock, backlog));
}
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
int posix_flags = 0;
if (sce_flags & ORBIS_NET_MSG_PEEK)
posix_flags |= MSG_PEEK;
#ifndef _WIN32
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
posix_flags |= MSG_DONTWAIT;
#endif
// MSG_WAITALL is only valid for stream sockets
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
posix_flags |= MSG_WAITALL;
return posix_flags;
}
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
// This function uses select() with zero timeout to simulate non-blocking behavior.
static int socket_is_ready(int sock, bool is_read = true) {
fd_set fds{};
FD_ZERO(&fds);
FD_SET(sock, &fds);
timeval timeout{0, 0};
int res =
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
if (res == 0) {
*Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK;
return -1;
} else if (res < 0) {
return ConvertReturnErrorCode(res);
}
return res;
}
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
int totalSent = 0;
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
// stream socket with multiple buffers
bool use_wsamsg =
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
msg->msg_iovlen > 1;
for (int i = 0; i < msg->msg_iovlen; ++i) {
char* buf = (char*)msg->msg_iov[i].iov_base;
size_t remaining = msg->msg_iov[i].iov_len;
while (remaining > 0) {
if (dontWait) {
int ready = socket_is_ready(sock, false);
if (ready <= 0)
return ready;
}
int sent = 0;
if (use_wsamsg) {
// only call WSASendMsg if we have multiple buffers
LPFN_WSASENDMSG wsasendmsg = nullptr;
GUID guid = WSAID_WSASENDMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
// fallback to send()
sent = ::send(sock, buf, remaining, 0);
} else {
DWORD bytesSent = 0;
int res = wsasendmsg(
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
&bytesSent, nullptr, nullptr);
if (res == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
sent = bytesSent;
}
} else {
sent = ::send(sock, buf, remaining, 0);
if (sent == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
}
totalSent += sent;
remaining -= sent;
buf += sent;
if (!waitAll)
break;
}
}
return totalSent;
#else
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
std::scoped_lock lock{m_mutex};
int res = 0;
#ifdef _WIN32
if (flags & ORBIS_NET_MSG_DONTWAIT) {
res = socket_is_ready(sock, false);
if (res <= 0)
return res;
}
#endif
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
if (to == nullptr) {
res = send(sock, (const char*)msg, len, posix_flags);
} else {
sockaddr addr{};
convertOrbisNetSockaddrToPosix(to, &addr);
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
}
return ConvertReturnErrorCode(res);
}
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{receive_mutex};
#ifdef _WIN32
int totalReceived = 0;
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
// stream socket with multiple buffers
bool use_wsarecvmsg =
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
msg->msg_iovlen > 1;
for (int i = 0; i < msg->msg_iovlen; ++i) {
char* buf = (char*)msg->msg_iov[i].iov_base;
size_t remaining = msg->msg_iov[i].iov_len;
while (remaining > 0) {
// emulate DONTWAIT
if (dontWait) {
int ready = socket_is_ready(sock, true);
if (ready <= 0)
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
}
int received = 0;
if (use_wsarecvmsg) {
// only call WSARecvMsg if multiple buffers + stream
LPFN_WSARECVMSG wsarecvmsg = nullptr;
GUID guid = WSAID_WSARECVMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
// fallback to recv()
received = ::recv(sock, buf, remaining, 0);
if (received == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
} else {
DWORD bytesReceived = 0;
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
nullptr, nullptr);
if (res == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
received = bytesReceived;
}
} else {
// fallback to recv() for UDP or single-buffer
received = ::recv(sock, buf, remaining, 0);
if (received == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
}
totalReceived += received;
remaining -= received;
buf += received;
// stop after first receive if WAITALL is not set
if (!waitAll)
break;
}
}
return totalReceived;
#else
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
u32* fromlen) {
std::scoped_lock lock{receive_mutex};
int res = 0;
#ifdef _WIN32
if (flags & ORBIS_NET_MSG_DONTWAIT) {
res = socket_is_ready(sock);
if (res <= 0)
return res;
}
#endif
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
if (from == nullptr) {
res = recv(sock, (char*)buf, len, posix_flags);
} else {
sockaddr addr{};
socklen_t addrlen = sizeof(addr);
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
if (res > 0)
convertPosixSockaddrToOrbis(&addr, from);
}
return ConvertReturnErrorCode(res);
}
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
socklen_t len = sizeof(addr2);
net_socket new_socket = ::accept(sock, &addr2, &len);
#ifdef _WIN32
if (new_socket != INVALID_SOCKET) {
#else
if (new_socket >= 0) {
#endif
if (addr && addrlen) {
convertPosixSockaddrToOrbis(&addr2, addr);
*addrlen = sizeof(OrbisNetSockaddrIn);
}
return std::make_shared<PosixSocket>(new_socket);
}
ConvertReturnErrorCode(new_socket);
return nullptr;
}
int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
convertOrbisNetSockaddrToPosix(addr, &addr2);
int result = ::connect(sock, &addr2, sizeof(sockaddr_in));
#ifdef _WIN32
// Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS
// Step in here on errors to address this.
if (result == -1) {
if (WSAGetLastError() == WSAEWOULDBLOCK) {
WSASetLastError(WSAEINPROGRESS);
}
}
#endif
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
result == -1 ? Common::GetLastErrorMsg() : "none");
return ConvertReturnErrorCode(result);
}
int PosixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr;
convertOrbisNetSockaddrToPosix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_in);
}
int res = getsockname(sock, &addr, (socklen_t*)namelen);
if (res >= 0) {
convertPosixSockaddrToOrbis(&addr, name);
*namelen = sizeof(OrbisNetSockaddrIn);
}
return ConvertReturnErrorCode(res);
}
#define CASE_SETSOCKOPT(opt) \
case ORBIS_NET_##opt: \
return ConvertReturnErrorCode( \
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
#define CASE_SETSOCKOPT_VALUE(opt, value) \
case opt: \
if (optlen != sizeof(*value)) { \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
memcpy(value, optval, optlen); \
return 0
int PosixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
std::scoped_lock lock{m_mutex};
s32 native_level = ConvertLevels(level);
::linger native_linger;
if (native_level == SOL_SOCKET) {
switch (optname) {
CASE_SETSOCKOPT(SO_REUSEADDR);
CASE_SETSOCKOPT(SO_KEEPALIVE);
CASE_SETSOCKOPT(SO_BROADCAST);
CASE_SETSOCKOPT(SO_SNDBUF);
CASE_SETSOCKOPT(SO_RCVBUF);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, &sockopt_so_connecttimeo);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, &sockopt_so_reuseport);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, &sockopt_so_usecrypto);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, &sockopt_so_usesignature);
case ORBIS_NET_SO_SNDTIMEO:
case ORBIS_NET_SO_RCVTIMEO: {
if (optlen != sizeof(int)) {
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
return -1;
}
std::vector<char> val;
const auto optname_nat = (optname == ORBIS_NET_SO_SNDTIMEO) ? SO_SNDTIMEO : SO_RCVTIMEO;
int timeout_us = *(const int*)optval;
#ifdef _WIN32
DWORD timeout = timeout_us / 1000;
#else
timeval timeout{.tv_sec = timeout_us / 1000000, .tv_usec = timeout_us % 1000000};
#endif
val.insert(val.end(), (char*)&timeout, (char*)&timeout + sizeof(timeout));
optlen = sizeof(timeout);
return ConvertReturnErrorCode(
setsockopt(sock, native_level, optname_nat, val.data(), optlen));
}
case ORBIS_NET_SO_ONESBCAST: {
if (optlen != sizeof(sockopt_so_onesbcast)) {
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
return -1;
}
memcpy(&sockopt_so_onesbcast, optval, optlen);
return ConvertReturnErrorCode(
setsockopt(sock, native_level, SO_BROADCAST, (const char*)optval, optlen));
}
case ORBIS_NET_SO_TYPE:
case ORBIS_NET_SO_ERROR: {
*Libraries::Kernel::__Error() = ORBIS_NET_ENOPROTOOPT;
return -1;
}
case ORBIS_NET_SO_LINGER: {
if (socket_type != ORBIS_NET_SOCK_STREAM) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
return -1;
}
if (optlen < sizeof(OrbisNetLinger)) {
LOG_ERROR(Lib_Net, "size missmatched! optlen = {} OrbisNetLinger={}", optlen,
sizeof(OrbisNetLinger));
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
}
const void* native_val = &native_linger;
u32 native_len = sizeof(native_linger);
native_linger.l_onoff = reinterpret_cast<const OrbisNetLinger*>(optval)->l_onoff;
native_linger.l_linger = reinterpret_cast<const OrbisNetLinger*>(optval)->l_linger;
return ConvertReturnErrorCode(
setsockopt(sock, native_level, SO_LINGER, (const char*)native_val, native_len));
}
case ORBIS_NET_SO_NAME:
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1; // don't support set for name
case ORBIS_NET_SO_NBIO: {
if (optlen < sizeof(sockopt_so_nbio)) {
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
} else {
memcpy(&sockopt_so_nbio, optval, sizeof(sockopt_so_nbio));
}
#ifdef _WIN32
static_assert(sizeof(u_long) == sizeof(sockopt_so_nbio),
"type used for ioctlsocket value does not have the expected size");
return ConvertReturnErrorCode(ioctlsocket(sock, FIONBIO, (u_long*)&sockopt_so_nbio));
#else
return ConvertReturnErrorCode(ioctl(sock, FIONBIO, &sockopt_so_nbio));
#endif
}
case ORBIS_NET_SO_ACCEPTTIMEO:
LOG_ERROR(Lib_Net, "Unhandled option ORBIS_NET_SO_ACCEPTTIMEO");
return ORBIS_OK;
}
} else if (native_level == IPPROTO_IP) {
switch (optname) {
// CASE_SETSOCKOPT(IP_HDRINCL);
CASE_SETSOCKOPT(IP_TOS);
CASE_SETSOCKOPT(IP_TTL);
CASE_SETSOCKOPT(IP_MULTICAST_IF);
CASE_SETSOCKOPT(IP_MULTICAST_TTL);
CASE_SETSOCKOPT(IP_MULTICAST_LOOP);
CASE_SETSOCKOPT(IP_ADD_MEMBERSHIP);
CASE_SETSOCKOPT(IP_DROP_MEMBERSHIP);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, &sockopt_ip_ttlchk);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, &sockopt_ip_maxttl);
case ORBIS_NET_IP_HDRINCL: {
if (socket_type != ORBIS_NET_SOCK_RAW) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
return -1;
}
return ConvertReturnErrorCode(
setsockopt(sock, native_level, optname, (const char*)optval, optlen));
}
}
} else if (native_level == IPPROTO_TCP) {
switch (optname) {
CASE_SETSOCKOPT(TCP_NODELAY);
CASE_SETSOCKOPT(TCP_MAXSEG);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, &sockopt_tcp_mss_to_advertise);
}
}
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
return 0;
}
#define CASE_GETSOCKOPT(opt) \
case ORBIS_NET_##opt: { \
socklen_t optlen_temp = *optlen; \
auto retval = ConvertReturnErrorCode( \
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
*optlen = optlen_temp; \
return retval; \
}
#define CASE_GETSOCKOPT_VALUE(opt, value) \
case opt: \
if (*optlen < sizeof(value)) { \
*optlen = sizeof(value); \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
*optlen = sizeof(value); \
*(decltype(value)*)optval = value; \
return 0;
int PosixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
std::scoped_lock lock{m_mutex};
s32 native_level = ConvertLevels(level);
if (native_level == SOL_SOCKET) {
switch (optname) {
CASE_GETSOCKOPT(SO_REUSEADDR);
CASE_GETSOCKOPT(SO_KEEPALIVE);
CASE_GETSOCKOPT(SO_BROADCAST);
CASE_GETSOCKOPT(SO_LINGER);
CASE_GETSOCKOPT(SO_SNDBUF);
CASE_GETSOCKOPT(SO_RCVBUF);
CASE_GETSOCKOPT(SO_SNDTIMEO);
CASE_GETSOCKOPT(SO_RCVTIMEO);
CASE_GETSOCKOPT(SO_ERROR);
CASE_GETSOCKOPT(SO_TYPE);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NBIO, sockopt_so_nbio);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, sockopt_so_connecttimeo);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, sockopt_so_reuseport);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_ONESBCAST, sockopt_so_onesbcast);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, sockopt_so_usecrypto);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, sockopt_so_usesignature);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NAME,
(char)0); // writes an empty string to the output buffer
case ORBIS_NET_SO_ERROR_EX: {
socklen_t optlen_temp = *optlen;
auto retval = ConvertReturnErrorCode(
getsockopt(sock, level, SO_ERROR, (char*)optval, &optlen_temp));
*optlen = optlen_temp;
if (retval < 0) {
s32 r = *Libraries::Kernel::__Error();
*Libraries::Kernel::__Error() = 0;
return r;
}
return retval;
}
}
} else if (native_level == IPPROTO_IP) {
switch (optname) {
CASE_GETSOCKOPT(IP_HDRINCL);
CASE_GETSOCKOPT(IP_TOS);
CASE_GETSOCKOPT(IP_TTL);
CASE_GETSOCKOPT(IP_MULTICAST_IF);
CASE_GETSOCKOPT(IP_MULTICAST_TTL);
CASE_GETSOCKOPT(IP_MULTICAST_LOOP);
CASE_GETSOCKOPT(IP_ADD_MEMBERSHIP);
CASE_GETSOCKOPT(IP_DROP_MEMBERSHIP);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, sockopt_ip_ttlchk);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, sockopt_ip_maxttl);
}
} else if (native_level == IPPROTO_TCP) {
switch (optname) {
CASE_GETSOCKOPT(TCP_NODELAY);
CASE_GETSOCKOPT(TCP_MAXSEG);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, sockopt_tcp_mss_to_advertise);
}
}
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
return 0;
}
int PosixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr;
convertOrbisNetSockaddrToPosix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_in);
}
int res = ::getpeername(sock, &addr, (socklen_t*)namelen);
if (res >= 0) {
convertPosixSockaddrToOrbis(&addr, name);
*namelen = sizeof(OrbisNetSockaddrIn);
}
return ConvertReturnErrorCode(res);
}
int PosixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
#ifdef _WIN32
LOG_ERROR(Lib_Net, "(STUBBED) called");
sb->st_mode = 0000777u | 0140000u;
return 0;
#else
struct stat st{};
int result = ::fstat(sock, &st);
sb->st_mode = 0000777u | 0140000u;
sb->st_size = st.st_size;
sb->st_blocks = st.st_blocks;
sb->st_blksize = st.st_blksize;
// sb->st_flags = st.st_flags;
return ConvertReturnErrorCode(result);
#endif
}
} // namespace Libraries::Net
@@ -0,0 +1,242 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
// clang-format off
#include <winsock2.h>
#include <ws2tcpip.h>
#include <afunix.h>
#include <iphlpapi.h>
#include <mstcpip.h>
// clang-format on
typedef SOCKET net_socket;
typedef int socklen_t;
#ifndef LPFN_WSASENDMSG
typedef INT(PASCAL* LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
LPDWORD lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
#endif
#ifndef WSAID_WSASENDMSG
static const GUID WSAID_WSASENDMSG = {
0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}};
#endif
#ifndef LPFN_WSARECVMSG
typedef INT(PASCAL* LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
#endif
#ifndef WSAID_WSARECVMSG
static const GUID WSAID_WSARECVMSG = {
0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}};
#endif
#else
#include <cerrno>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
typedef int net_socket;
#endif
#include <condition_variable>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <vector>
#include "net.h"
namespace Libraries::Kernel {
struct OrbisKernelStat;
}
namespace Libraries::Net {
struct Socket;
typedef std::shared_ptr<Socket> SocketPtr;
struct OrbisNetLinger {
s32 l_onoff;
s32 l_linger;
};
struct Socket {
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
virtual ~Socket() = default;
virtual bool IsValid() const = 0;
virtual int Close() = 0;
virtual int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) = 0;
virtual int GetSocketOptions(int level, int optname, void* optval, u32* optlen) = 0;
virtual int Bind(const OrbisNetSockaddr* addr, u32 addrlen) = 0;
virtual int Listen(int backlog) = 0;
virtual int SendMessage(const OrbisNetMsghdr* msg, int flags) = 0;
virtual int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) = 0;
virtual SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) = 0;
virtual int ReceiveMessage(OrbisNetMsghdr* msg, int flags) = 0;
virtual int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
u32* fromlen) = 0;
virtual int Connect(const OrbisNetSockaddr* addr, u32 namelen) = 0;
virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0;
virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0;
virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0;
virtual std::optional<net_socket> Native() = 0;
std::mutex m_mutex;
std::mutex receive_mutex;
int socket_type;
};
struct PosixSocket : public Socket {
net_socket sock;
int sockopt_so_connecttimeo = 0;
int sockopt_so_reuseport = 0;
int sockopt_so_onesbcast = 0;
int sockopt_so_usecrypto = 0;
int sockopt_so_usesignature = 0;
int sockopt_so_nbio = 0;
int sockopt_ip_ttlchk = 0;
int sockopt_ip_maxttl = 0;
int sockopt_tcp_mss_to_advertise = 0;
int socket_type;
explicit PosixSocket(int domain, int type, int protocol)
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
socket_type = type;
}
explicit PosixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
bool IsValid() const override;
int Close() override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return sock;
}
};
struct P2PDatagram {
u32 source_addr{};
u16 source_port{};
u16 source_vport{};
std::vector<u8> payload;
};
struct P2PSocket : public Socket {
explicit P2PSocket(int domain, int type, int protocol);
~P2PSocket() override;
bool IsValid() const override {
return valid;
}
int Close() override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
#ifdef _WIN32
if (readiness_fd != INVALID_SOCKET) {
return readiness_fd;
}
#elif defined(__linux__)
if (readiness_fd >= 0) {
return readiness_fd;
}
#endif
return {};
}
// Called by the shared UDP transport after it demultiplexes a virtual port.
void EnqueuePacket(P2PDatagram packet);
private:
bool valid{true};
bool bound{false};
bool connected{false};
OrbisNetSockaddrIn local_addr{};
OrbisNetSockaddrIn peer_addr{};
int sockopt_so_nbio{0};
int sockopt_so_usecrypto{0};
int sockopt_so_usesignature{0};
std::map<int, int> integer_socket_options;
std::condition_variable receive_cv;
std::deque<P2PDatagram> receive_queue;
#ifdef _WIN32
// wepoll requires a Winsock handle. A private loopback pair carries readiness bytes only.
net_socket readiness_fd{INVALID_SOCKET};
net_socket readiness_signal_fd{INVALID_SOCKET};
#elif defined(__linux__)
// eventfd carries readiness only; network data stays on the shared UDP transport.
int readiness_fd{-1};
#endif
};
struct UnixSocket : public Socket {
net_socket sock;
int socket_type;
explicit UnixSocket(int domain, int type, int protocol)
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
socket_type = type;
}
explicit UnixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
bool IsValid() const override;
int Close() override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return sock;
}
};
u16 GetP2PConfiguredPort();
u32 GetP2PAdvertisedAddr();
bool EnsureP2PTransport();
bool P2PTransportIsReady();
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
} // namespace Libraries::Net
File diff suppressed because it is too large. Load diff
+231
View File
@@ -0,0 +1,231 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Ssl {
struct OrbisSslCaCerts;
struct OrbisSslCaList {
void* certs;
int certsNum;
};
int PS4_SYSV_ABI CA_MGMT_allocCertDistinguishedName();
int PS4_SYSV_ABI CA_MGMT_certDistinguishedNameCompare();
int PS4_SYSV_ABI CA_MGMT_convertKeyBlobToPKCS8Key();
int PS4_SYSV_ABI CA_MGMT_convertKeyDER();
int PS4_SYSV_ABI CA_MGMT_convertKeyPEM();
int PS4_SYSV_ABI CA_MGMT_convertPKCS8KeyToKeyBlob();
int PS4_SYSV_ABI CA_MGMT_convertProtectedPKCS8KeyToKeyBlob();
int PS4_SYSV_ABI CA_MGMT_decodeCertificate();
int PS4_SYSV_ABI CA_MGMT_enumAltName();
int PS4_SYSV_ABI CA_MGMT_enumCrl();
int PS4_SYSV_ABI CA_MGMT_extractAllCertDistinguishedName();
int PS4_SYSV_ABI CA_MGMT_extractBasicConstraint();
int PS4_SYSV_ABI CA_MGMT_extractCertASN1Name();
int PS4_SYSV_ABI CA_MGMT_extractCertTimes();
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobEx();
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobTypeEx();
int PS4_SYSV_ABI CA_MGMT_extractPublicKeyInfo();
int PS4_SYSV_ABI CA_MGMT_extractSerialNum();
int PS4_SYSV_ABI CA_MGMT_extractSignature();
int PS4_SYSV_ABI CA_MGMT_free();
int PS4_SYSV_ABI CA_MGMT_freeCertDistinguishedName();
int PS4_SYSV_ABI CA_MGMT_freeCertDistinguishedNameOnStack();
int PS4_SYSV_ABI CA_MGMT_freeCertificate();
int PS4_SYSV_ABI CA_MGMT_freeKeyBlob();
int PS4_SYSV_ABI CA_MGMT_freeSearchDetails();
int PS4_SYSV_ABI CA_MGMT_getCertSignAlgoType();
int PS4_SYSV_ABI CA_MGMT_keyBlobToDER();
int PS4_SYSV_ABI CA_MGMT_keyBlobToPEM();
int PS4_SYSV_ABI CA_MGMT_makeKeyBlobEx();
int PS4_SYSV_ABI CA_MGMT_rawVerifyOID();
int PS4_SYSV_ABI CA_MGMT_reorderChain();
int PS4_SYSV_ABI CA_MGMT_returnCertificatePrints();
int PS4_SYSV_ABI CA_MGMT_verifyCertWithKeyBlob();
int PS4_SYSV_ABI CA_MGMT_verifySignature();
int PS4_SYSV_ABI CERT_checkCertificateIssuer();
int PS4_SYSV_ABI CERT_checkCertificateIssuer2();
int PS4_SYSV_ABI CERT_checkCertificateIssuerSerialNumber();
int PS4_SYSV_ABI CERT_CompSubjectAltNames();
int PS4_SYSV_ABI CERT_CompSubjectAltNames2();
int PS4_SYSV_ABI CERT_CompSubjectAltNamesExact();
int PS4_SYSV_ABI CERT_CompSubjectCommonName();
int PS4_SYSV_ABI CERT_CompSubjectCommonName2();
int PS4_SYSV_ABI CERT_ComputeBufferHash();
int PS4_SYSV_ABI CERT_decryptRSASignature();
int PS4_SYSV_ABI CERT_decryptRSASignatureBuffer();
int PS4_SYSV_ABI CERT_enumerateAltName();
int PS4_SYSV_ABI CERT_enumerateAltName2();
int PS4_SYSV_ABI CERT_enumerateCRL();
int PS4_SYSV_ABI CERT_enumerateCRL2();
int PS4_SYSV_ABI CERT_enumerateCRLAux();
int PS4_SYSV_ABI CERT_extractAllDistinguishedNames();
int PS4_SYSV_ABI CERT_extractDistinguishedNames();
int PS4_SYSV_ABI CERT_extractDistinguishedNames2();
int PS4_SYSV_ABI CERT_extractDistinguishedNamesFromName();
int PS4_SYSV_ABI CERT_extractRSAKey();
int PS4_SYSV_ABI CERT_extractSerialNum();
int PS4_SYSV_ABI CERT_extractSerialNum2();
int PS4_SYSV_ABI CERT_extractValidityTime();
int PS4_SYSV_ABI CERT_extractValidityTime2();
int PS4_SYSV_ABI CERT_getCertExtension();
int PS4_SYSV_ABI CERT_getCertificateExtensions();
int PS4_SYSV_ABI CERT_getCertificateExtensions2();
int PS4_SYSV_ABI CERT_getCertificateIssuerSerialNumber();
int PS4_SYSV_ABI CERT_getCertificateIssuerSerialNumber2();
int PS4_SYSV_ABI CERT_getCertificateKeyUsage();
int PS4_SYSV_ABI CERT_getCertificateKeyUsage2();
int PS4_SYSV_ABI CERT_getCertificateSubject();
int PS4_SYSV_ABI CERT_getCertificateSubject2();
int PS4_SYSV_ABI CERT_getCertSignAlgoType();
int PS4_SYSV_ABI CERT_GetCertTime();
int PS4_SYSV_ABI CERT_getNumberOfChild();
int PS4_SYSV_ABI CERT_getRSASignatureAlgo();
int PS4_SYSV_ABI CERT_getSignatureItem();
int PS4_SYSV_ABI CERT_getSubjectCommonName();
int PS4_SYSV_ABI CERT_getSubjectCommonName2();
int PS4_SYSV_ABI CERT_isRootCertificate();
int PS4_SYSV_ABI CERT_isRootCertificate2();
int PS4_SYSV_ABI CERT_rawVerifyOID();
int PS4_SYSV_ABI CERT_rawVerifyOID2();
int PS4_SYSV_ABI CERT_setKeyFromSubjectPublicKeyInfo();
int PS4_SYSV_ABI CERT_setKeyFromSubjectPublicKeyInfoCert();
int PS4_SYSV_ABI CERT_STORE_addCertAuthority();
int PS4_SYSV_ABI CERT_STORE_addIdentity();
int PS4_SYSV_ABI CERT_STORE_addIdentityNakedKey();
int PS4_SYSV_ABI CERT_STORE_addIdentityPSK();
int PS4_SYSV_ABI CERT_STORE_addIdentityWithCertificateChain();
int PS4_SYSV_ABI CERT_STORE_addTrustPoint();
int PS4_SYSV_ABI CERT_STORE_createStore();
int PS4_SYSV_ABI CERT_STORE_findCertBySubject();
int PS4_SYSV_ABI CERT_STORE_findIdentityByTypeFirst();
int PS4_SYSV_ABI CERT_STORE_findIdentityByTypeNext();
int PS4_SYSV_ABI CERT_STORE_findIdentityCertChainFirst();
int PS4_SYSV_ABI CERT_STORE_findIdentityCertChainNext();
int PS4_SYSV_ABI CERT_STORE_findPskByIdentity();
int PS4_SYSV_ABI CERT_STORE_releaseStore();
int PS4_SYSV_ABI CERT_STORE_traversePskListHead();
int PS4_SYSV_ABI CERT_STORE_traversePskListNext();
int PS4_SYSV_ABI CERT_validateCertificate();
int PS4_SYSV_ABI CERT_validateCertificateWithConf();
int PS4_SYSV_ABI CERT_VerifyCertificatePolicies();
int PS4_SYSV_ABI CERT_VerifyCertificatePolicies2();
int PS4_SYSV_ABI CERT_verifySignature();
int PS4_SYSV_ABI CERT_VerifyValidityTime();
int PS4_SYSV_ABI CERT_VerifyValidityTime2();
int PS4_SYSV_ABI CERT_VerifyValidityTimeWithConf();
int PS4_SYSV_ABI CRYPTO_initAsymmetricKey();
int PS4_SYSV_ABI CRYPTO_uninitAsymmetricKey();
int PS4_SYSV_ABI GC_createInstanceIDs();
int PS4_SYSV_ABI getCertSigAlgo();
int PS4_SYSV_ABI MOCANA_freeMocana();
int PS4_SYSV_ABI MOCANA_initMocana();
int PS4_SYSV_ABI RSA_verifySignature();
int PS4_SYSV_ABI sceSslCheckRecvPending();
int PS4_SYSV_ABI sceSslClose();
int PS4_SYSV_ABI sceSslConnect();
int PS4_SYSV_ABI sceSslCreateSslConnection();
int PS4_SYSV_ABI sceSslDeleteSslConnection();
int PS4_SYSV_ABI sceSslDisableOption();
int PS4_SYSV_ABI sceSslDisableOptionInternal();
int PS4_SYSV_ABI sceSslDisableOptionInternalInsecure();
int PS4_SYSV_ABI sceSslEnableOption();
int PS4_SYSV_ABI sceSslEnableOptionInternal();
int PS4_SYSV_ABI sceSslFreeCaCerts();
int PS4_SYSV_ABI sceSslFreeCaList();
int PS4_SYSV_ABI sceSslFreeSslCertName();
int PS4_SYSV_ABI sceSslGetCaCerts(int sslCtxId, OrbisSslCaCerts* certs);
int PS4_SYSV_ABI sceSslGetCaList();
int PS4_SYSV_ABI sceSslGetIssuerName();
int PS4_SYSV_ABI sceSslGetMemoryPoolStats();
int PS4_SYSV_ABI sceSslGetNameEntryCount();
int PS4_SYSV_ABI sceSslGetNameEntryInfo();
int PS4_SYSV_ABI sceSslGetNanoSSLModuleId();
int PS4_SYSV_ABI sceSslGetNotAfter();
int PS4_SYSV_ABI sceSslGetNotBefore();
int PS4_SYSV_ABI sceSslGetSerialNumber();
int PS4_SYSV_ABI sceSslGetSslError();
int PS4_SYSV_ABI sceSslGetSubjectName();
int PS4_SYSV_ABI sceSslInit(std::size_t poolSize);
int PS4_SYSV_ABI sceSslLoadCert();
int PS4_SYSV_ABI sceSslLoadRootCACert();
int PS4_SYSV_ABI sceSslRecv();
int PS4_SYSV_ABI sceSslReuseConnection();
int PS4_SYSV_ABI sceSslSend();
int PS4_SYSV_ABI sceSslSetMinSslVersion();
int PS4_SYSV_ABI sceSslSetSslVersion();
int PS4_SYSV_ABI sceSslSetVerifyCallback();
int PS4_SYSV_ABI sceSslShowMemoryStat();
int PS4_SYSV_ABI sceSslTerm();
int PS4_SYSV_ABI sceSslUnloadCert();
int PS4_SYSV_ABI SSL_acceptConnection();
int PS4_SYSV_ABI SSL_acceptConnectionCommon();
int PS4_SYSV_ABI SSL_assignCertificateStore();
int PS4_SYSV_ABI SSL_ASYNC_acceptConnection();
int PS4_SYSV_ABI SSL_ASYNC_closeConnection();
int PS4_SYSV_ABI SSL_ASYNC_connect();
int PS4_SYSV_ABI SSL_ASYNC_connectCommon();
int PS4_SYSV_ABI SSL_ASYNC_getRecvBuffer();
int PS4_SYSV_ABI SSL_ASYNC_getSendBuffer();
int PS4_SYSV_ABI SSL_ASYNC_init();
int PS4_SYSV_ABI SSL_ASYNC_initServer();
int PS4_SYSV_ABI SSL_ASYNC_recvMessage();
int PS4_SYSV_ABI SSL_ASYNC_recvMessage2();
int PS4_SYSV_ABI SSL_ASYNC_sendMessage();
int PS4_SYSV_ABI SSL_ASYNC_sendMessagePending();
int PS4_SYSV_ABI SSL_ASYNC_start();
int PS4_SYSV_ABI SSL_closeConnection();
int PS4_SYSV_ABI SSL_connect();
int PS4_SYSV_ABI SSL_connectWithCfgParam();
int PS4_SYSV_ABI SSL_enableCiphers();
int PS4_SYSV_ABI SSL_findConnectionInstance();
int PS4_SYSV_ABI SSL_getCipherInfo();
int PS4_SYSV_ABI SSL_getClientRandom();
int PS4_SYSV_ABI SSL_getClientSessionInfo();
int PS4_SYSV_ABI SSL_getCookie();
int PS4_SYSV_ABI SSL_getNextSessionId();
int PS4_SYSV_ABI SSL_getServerRandom();
int PS4_SYSV_ABI SSL_getSessionCache();
int PS4_SYSV_ABI SSL_getSessionFlags();
int PS4_SYSV_ABI SSL_getSessionInfo();
int PS4_SYSV_ABI SSL_getSessionStatus();
int PS4_SYSV_ABI SSL_getSocketId();
int PS4_SYSV_ABI SSL_getSSLTLSVersion();
int PS4_SYSV_ABI SSL_init();
int PS4_SYSV_ABI SSL_initiateRehandshake();
int PS4_SYSV_ABI SSL_initServerCert();
int PS4_SYSV_ABI SSL_ioctl();
int PS4_SYSV_ABI SSL_isSessionSSL();
int PS4_SYSV_ABI SSL_lockSessionCacheMutex();
int PS4_SYSV_ABI SSL_lookupAlert();
int PS4_SYSV_ABI SSL_negotiateConnection();
int PS4_SYSV_ABI SSL_recv();
int PS4_SYSV_ABI SSL_recvPending();
int PS4_SYSV_ABI SSL_releaseTables();
int PS4_SYSV_ABI SSL_retrieveServerNameList();
int PS4_SYSV_ABI SSL_rngFun();
int PS4_SYSV_ABI SSL_send();
int PS4_SYSV_ABI SSL_sendAlert();
int PS4_SYSV_ABI SSL_sendPending();
int PS4_SYSV_ABI SSL_setCookie();
int PS4_SYSV_ABI SSL_setServerCert();
int PS4_SYSV_ABI SSL_setServerNameList();
int PS4_SYSV_ABI SSL_setSessionFlags();
int PS4_SYSV_ABI SSL_shutdown();
int PS4_SYSV_ABI SSL_sslSettings();
int PS4_SYSV_ABI SSL_validateCertParam();
int PS4_SYSV_ABI VLONG_freeVlongQueue();
int PS4_SYSV_ABI Func_22E76E60BC0587D7();
int PS4_SYSV_ABI Func_28F8791A771D39C7();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Ssl
+375
View File
@@ -0,0 +1,375 @@
// SPDX-FileCopyrightText: Copyright 2024 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/network/ssl2.h"
#include "core/libraries/network/ssl2_error.h"
namespace Libraries::Ssl2 {
int PS4_SYSV_ABI CA_MGMT_extractKeyBlobEx() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI CA_MGMT_extractPublicKeyInfo() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI CA_MGMT_freeKeyBlob() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI CRYPTO_initAsymmetricKey() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI CRYPTO_uninitAsymmetricKey() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI RSA_verifySignature() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslCheckRecvPending() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslClose() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslConnect() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslCreateConnection() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslCreateSslConnection() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDeleteConnection() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDeleteSslConnection() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDisableOption() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDisableOptionInternal() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDisableOptionInternalInsecure() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslDisableVerifyOption() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslEnableOption() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslEnableOptionInternal() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslEnableVerifyOption() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
if (certs == nullptr) {
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
}
if (certs->certs != nullptr) {
for (s32 data = 0; data < certs->num; data++) {
free(certs->certs[data].ptr);
}
delete (certs->certs);
}
// delete (certs->pool);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslFreeCaList() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslFreeSslCertName() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetAlpnSelected() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) {
LOG_ERROR(Lib_Ssl2, "(DUMMY) called");
if (certs == nullptr) {
return ORBIS_SSL_ERROR_INVALID_ARGUMENT;
}
// Allocate a buffer to store dummy data in.
const char* dummy_data = "dummy";
u64 dummy_length = strlen(dummy_data) + 1;
char* data = static_cast<char*>(malloc(dummy_length));
strncpy(data, dummy_data, dummy_length);
certs->certs = new OrbisSslData{data, dummy_length};
certs->num = 1;
certs->pool = nullptr;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetCaList() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetFingerprint() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetIssuerName() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetMemoryPoolStats() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetNameEntryCount() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetNameEntryInfo() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetNanoSSLModuleId() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetNotAfter() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetNotBefore() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetPeerCert() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetPem() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetSerialNumber() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetSslError() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslGetSubjectName() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslInit(std::size_t poolSize) {
LOG_ERROR(Lib_Ssl2, "(DUMMY) called poolSize = {}", poolSize);
// return a value >1
static int id = 0;
return ++id;
}
int PS4_SYSV_ABI sceSslLoadCert() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslLoadRootCACert() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslRead() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslRecv() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslReuseConnection() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslSend() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslSetAlpn() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslSetMinSslVersion() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslSetSslVersion() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslSetVerifyCallback() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslTerm() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslUnloadCert() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSslWrite() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI VLONG_freeVlongQueue() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI Func_22E76E60BC0587D7() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI Func_28F8791A771D39C7() {
LOG_ERROR(Lib_Ssl2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Md+HYkCBZB4", "libSceSsl", 1, "libSceSsl", CA_MGMT_extractKeyBlobEx);
LIB_FUNCTION("9bKYzKP6kYU", "libSceSsl", 1, "libSceSsl", CA_MGMT_extractPublicKeyInfo);
LIB_FUNCTION("ipLIammTj2Q", "libSceSsl", 1, "libSceSsl", CA_MGMT_freeKeyBlob);
LIB_FUNCTION("PRWr3-ytpdg", "libSceSsl", 1, "libSceSsl", CRYPTO_initAsymmetricKey);
LIB_FUNCTION("cW7VCIMCh9A", "libSceSsl", 1, "libSceSsl", CRYPTO_uninitAsymmetricKey);
LIB_FUNCTION("pBwtarKd7eg", "libSceSsl", 1, "libSceSsl", RSA_verifySignature);
LIB_FUNCTION("1VM0h1JrUfA", "libSceSsl", 1, "libSceSsl", sceSslCheckRecvPending);
LIB_FUNCTION("viRXSHZYd0c", "libSceSsl", 1, "libSceSsl", sceSslClose);
LIB_FUNCTION("zXvd6iNyfgc", "libSceSsl", 1, "libSceSsl", sceSslConnect);
LIB_FUNCTION("tuscfitnhEo", "libSceSsl", 1, "libSceSsl", sceSslCreateConnection);
LIB_FUNCTION("P14ATpXc4J8", "libSceSsl", 1, "libSceSsl", sceSslCreateSslConnection);
LIB_FUNCTION("HJ1n138CQ2g", "libSceSsl", 1, "libSceSsl", sceSslDeleteConnection);
LIB_FUNCTION("hwrHV6Pprk4", "libSceSsl", 1, "libSceSsl", sceSslDeleteSslConnection);
LIB_FUNCTION("iLKz4+ukLqk", "libSceSsl", 1, "libSceSsl", sceSslDisableOption);
LIB_FUNCTION("-WqxBRAUVM4", "libSceSsl", 1, "libSceSsl", sceSslDisableOptionInternal);
LIB_FUNCTION("w1+L-27nYas", "libSceSsl", 1, "libSceSsl", sceSslDisableOptionInternalInsecure);
LIB_FUNCTION("PwsHbErG+e8", "libSceSsl", 1, "libSceSsl", sceSslDisableVerifyOption);
LIB_FUNCTION("m-zPyAsIpco", "libSceSsl", 1, "libSceSsl", sceSslEnableOption);
LIB_FUNCTION("g-zCwUKstEQ", "libSceSsl", 1, "libSceSsl", sceSslEnableOptionInternal);
LIB_FUNCTION("po1X86mgHDU", "libSceSsl", 1, "libSceSsl", sceSslEnableVerifyOption);
LIB_FUNCTION("qIvLs0gYxi0", "libSceSsl", 1, "libSceSsl", sceSslFreeCaCerts);
LIB_FUNCTION("+DzXseDVkeI", "libSceSsl", 1, "libSceSsl", sceSslFreeCaList);
LIB_FUNCTION("RwXD8grHZHM", "libSceSsl", 1, "libSceSsl", sceSslFreeSslCertName);
LIB_FUNCTION("4O7+bRkRUe8", "libSceSsl", 1, "libSceSsl", sceSslGetAlpnSelected);
LIB_FUNCTION("TDfQqO-gMbY", "libSceSsl", 1, "libSceSsl", sceSslGetCaCerts);
LIB_FUNCTION("qOn+wm28wmA", "libSceSsl", 1, "libSceSsl", sceSslGetCaList);
LIB_FUNCTION("brRtwGBu4A8", "libSceSsl", 1, "libSceSsl", sceSslGetFingerprint);
LIB_FUNCTION("7whYpYfHP74", "libSceSsl", 1, "libSceSsl", sceSslGetIssuerName);
LIB_FUNCTION("-PoIzr3PEk0", "libSceSsl", 1, "libSceSsl", sceSslGetMemoryPoolStats);
LIB_FUNCTION("R1ePzopYPYM", "libSceSsl", 1, "libSceSsl", sceSslGetNameEntryCount);
LIB_FUNCTION("7RBSTKGrmDA", "libSceSsl", 1, "libSceSsl", sceSslGetNameEntryInfo);
LIB_FUNCTION("AzUipl-DpIw", "libSceSsl", 1, "libSceSsl", sceSslGetNanoSSLModuleId);
LIB_FUNCTION("xHpt6+2pGYk", "libSceSsl", 1, "libSceSsl", sceSslGetNotAfter);
LIB_FUNCTION("Eo0S65Jy28Q", "libSceSsl", 1, "libSceSsl", sceSslGetNotBefore);
LIB_FUNCTION("-TbZc8pwPNc", "libSceSsl", 1, "libSceSsl", sceSslGetPeerCert);
LIB_FUNCTION("kLB5aGoUJXg", "libSceSsl", 1, "libSceSsl", sceSslGetPem);
LIB_FUNCTION("DOwXL+FQMEY", "libSceSsl", 1, "libSceSsl", sceSslGetSerialNumber);
LIB_FUNCTION("0XcZknp7-Wc", "libSceSsl", 1, "libSceSsl", sceSslGetSslError);
LIB_FUNCTION("dQReuBX9sD8", "libSceSsl", 1, "libSceSsl", sceSslGetSubjectName);
LIB_FUNCTION("hdpVEUDFW3s", "libSceSsl", 1, "libSceSsl", sceSslInit);
LIB_FUNCTION("Ab7+DH+gYyM", "libSceSsl", 1, "libSceSsl", sceSslLoadCert);
LIB_FUNCTION("3-643mGVFJo", "libSceSsl", 1, "libSceSsl", sceSslLoadRootCACert);
LIB_FUNCTION("jltWpVKtetg", "libSceSsl", 1, "libSceSsl", sceSslRead);
LIB_FUNCTION("hi0veU3L2pU", "libSceSsl", 1, "libSceSsl", sceSslRecv);
LIB_FUNCTION("50R2xYaYZwE", "libSceSsl", 1, "libSceSsl", sceSslReuseConnection);
LIB_FUNCTION("p5bM5PPufFY", "libSceSsl", 1, "libSceSsl", sceSslSend);
LIB_FUNCTION("TL86glUrmUw", "libSceSsl", 1, "libSceSsl", sceSslSetAlpn);
LIB_FUNCTION("QWSxBzf6lAg", "libSceSsl", 1, "libSceSsl", sceSslSetMinSslVersion);
LIB_FUNCTION("bKaEtQnoUuQ", "libSceSsl", 1, "libSceSsl", sceSslSetSslVersion);
LIB_FUNCTION("E4a-ahM57QQ", "libSceSsl", 1, "libSceSsl", sceSslSetVerifyCallback);
LIB_FUNCTION("0K1yQ6Lv-Yc", "libSceSsl", 1, "libSceSsl", sceSslTerm);
LIB_FUNCTION("UQ+3Qu7v3cA", "libSceSsl", 1, "libSceSsl", sceSslUnloadCert);
LIB_FUNCTION("iNjkt9Poblw", "libSceSsl", 1, "libSceSsl", sceSslWrite);
LIB_FUNCTION("wcVuyTUr5ys", "libSceSsl", 1, "libSceSsl", VLONG_freeVlongQueue);
LIB_FUNCTION("IuduYLwFh9c", "libSceSsl", 1, "libSceSsl", Func_22E76E60BC0587D7);
LIB_FUNCTION("KPh5GncdOcc", "libSceSsl", 1, "libSceSsl", Func_28F8791A771D39C7);
};
} // namespace Libraries::Ssl2
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Ssl2 {
struct OrbisSslData {
char* ptr;
u64 size;
};
struct OrbisSslCaCerts {
OrbisSslData* certs;
u64 num;
void* pool;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Ssl2
@@ -0,0 +1,8 @@
// 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_SSL_ERROR_INVALID_ARGUMENT = 0x8095F007;
@@ -0,0 +1,440 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/assert.h>
#include <common/logging/log.h>
#include <core/libraries/kernel/kernel.h>
#include <magic_enum/magic_enum.hpp>
#include "common/error.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "net_error.h"
#include "sockets.h"
#include "sys_net.h"
namespace Libraries::Net {
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Connect(addr, addrlen);
if (returncode >= 0) {
return returncode;
}
u32 error = *Libraries::Kernel::__Error();
// Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication.
if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) {
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
}
return -1;
}
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Bind(addr, addrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
auto new_sock = file->socket->Accept(addr, paddrlen);
if (!new_sock) {
u32 error = *Libraries::Kernel::__Error();
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
if (error != ORBIS_NET_EWOULDBLOCK) {
LOG_ERROR(Lib_Net,
"s = {} ({}) returned error code creating new socket for accepting: {}", s,
file->m_guest_name, error);
}
return -1;
}
auto fd = FDTable::Instance()->CreateHandle();
auto* new_file = FDTable::Instance()->GetFile(fd);
new_file->is_opened = true;
new_file->type = Core::FileSys::FileType::Socket;
new_file->socket = new_sock;
return fd;
}
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->GetPeerName(addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->GetSocketAddress(addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
auto name = [&] -> std::string_view {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return NameOf((OrbisNetSocketSoOption)optname);
case ORBIS_NET_IPPROTO_IP:
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
case ORBIS_NET_IPPROTO_TCP:
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
default:
return std::string_view{"unknown"};
}
}();
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
NameOf((OrbisNetProtocol)level), name);
int returncode = file->socket->GetSocketOptions(level, optname, optval, optlen);
if (returncode >= 0) {
if (optname == ORBIS_NET_SO_ERROR) {
LOG_DEBUG(Lib_Net, "so_error = {}", *reinterpret_cast<s32*>(optval));
}
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), backlog = {}", s, file->m_guest_name, backlog);
int returncode = file->socket->Listen(backlog);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
auto name = [&] -> std::string_view {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return NameOf((OrbisNetSocketSoOption)optname);
case ORBIS_NET_IPPROTO_IP:
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
case ORBIS_NET_IPPROTO_TCP:
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
default:
return std::string_view{"unknown"};
}
}();
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
NameOf((OrbisNetProtocol)level), name);
int returncode = file->socket->SetSocketOptions(level, optname, optval, optlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
return -1;
}
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) {
auto sname = name ? name : "anon";
LOG_INFO(Lib_Net, "name = {} family = {} type = {} protocol = {}", std::string(sname),
magic_enum::enum_name((OrbisNetFamily)family),
magic_enum::enum_name((OrbisNetSocketType)type), protocol);
SocketPtr socket;
switch (type) {
case ORBIS_NET_SOCK_STREAM:
case ORBIS_NET_SOCK_DGRAM:
if (family == ORBIS_NET_AF_UNIX) {
socket = std::make_shared<UnixSocket>(family, type, protocol);
break;
}
[[fallthrough]];
case ORBIS_NET_SOCK_RAW:
socket = std::make_shared<PosixSocket>(family, type, protocol);
break;
case ORBIS_NET_SOCK_DGRAM_P2P:
case ORBIS_NET_SOCK_STREAM_P2P:
socket = std::make_shared<P2PSocket>(family, type, protocol);
break;
default:
UNREACHABLE_MSG("Unknown type {}", type);
}
if (!socket->IsValid()) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
auto fd = FDTable::Instance()->CreateHandle();
auto* sock = FDTable::Instance()->GetFile(fd);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = socket;
sock->m_guest_name = sname;
return fd;
}
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) {
return sys_socketex(nullptr, family, type, protocol);
}
#ifdef _WIN32
int socketpair(int family, int type, int protocol, net_socket fd[2]) {
if (family != AF_UNIX && family != AF_INET) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
if (type != SOCK_STREAM) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
SOCKET listener = INVALID_SOCKET;
SOCKET sock1 = INVALID_SOCKET;
SOCKET sock2 = INVALID_SOCKET;
sockaddr_in addr{};
int addrlen = sizeof(addr);
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == INVALID_SOCKET)
goto fail1;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
goto fail1;
if (listen(listener, 1) == SOCKET_ERROR)
goto fail1;
if (getsockname(listener, (sockaddr*)&addr, &addrlen) == SOCKET_ERROR)
goto fail1;
sock1 = socket(AF_INET, SOCK_STREAM, 0);
if (sock1 == INVALID_SOCKET)
goto fail2;
if (connect(sock1, (sockaddr*)&addr, addrlen) == SOCKET_ERROR)
goto fail2;
sock2 = accept(listener, nullptr, nullptr);
if (sock2 == INVALID_SOCKET)
goto fail3;
closesocket(listener);
fd[0] = sock1;
fd[1] = sock2;
return 0;
fail3:
closesocket(sock2);
fail2:
closesocket(sock1);
fail1:
closesocket(listener);
return -1;
}
#endif
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) {
if (sv == nullptr) {
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
}
if (family != ORBIS_NET_AF_UNIX) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
net_socket fd[2];
if (socketpair(family, type, protocol, fd) != 0) {
LOG_ERROR(Lib_Net, "socketpair failed with {}", Common::GetLastErrorMsg());
return -1;
}
auto fd1 = FDTable::Instance()->CreateHandle();
auto fd2 = FDTable::Instance()->CreateHandle();
auto* sock = FDTable::Instance()->GetFile(fd1);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = std::make_shared<UnixSocket>(fd[0]);
sock->m_guest_name = "anon_sock0";
sock = FDTable::Instance()->GetFile(fd2);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = std::make_shared<UnixSocket>(fd[1]);
sock->m_guest_name = "anon_sock1";
sv[0] = fd1;
sv[1] = fd2;
return 0;
}
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return -1;
}
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Close();
if (returncode >= 0) {
FDTable::Instance()->DeleteHandle(s);
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
return sys_sendto(s, buf, len, flags, nullptr, 0);
}
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
int returncode = file->socket->SendPacket(buf, len, flags, addr, addrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
int returncode = file->socket->SendMessage(msg, flags);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) {
return sys_recvfrom(s, buf, len, flags, nullptr, nullptr);
}
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
int returncode = file->socket->ReceivePacket(buf, len, flags, addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
u32 error = *Libraries::Kernel::__Error();
if (error != ORBIS_NET_EWOULDBLOCK) {
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
}
return -1;
}
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
int returncode = file->socket->ReceiveMessage(msg, flags);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
(u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_htons(u16 v) {
return ((v << 8) & 0xff00) | ((v >> 8) & 0x00ff);
}
int PS4_SYSV_ABI sys_htonl(u32 v) {
return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24);
}
} // namespace Libraries::Net
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "net.h"
namespace Libraries::Net {
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog);
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen);
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how);
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol);
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol);
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]);
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags);
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s);
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags);
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags);
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen);
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
int PS4_SYSV_ABI sys_htons(u16 v);
int PS4_SYSV_ABI sys_htonl(u32 v);
} // namespace Libraries::Net
@@ -0,0 +1,393 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/assert.h>
#include "common/error.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "net.h"
#ifndef _WIN32
#include <sys/stat.h>
#include <sys/un.h>
#endif
#include "net_error.h"
#include "sockets.h"
namespace Libraries::Net {
#ifdef _WIN32
#define ERROR_CASE(errname) \
case (WSA##errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#else
#define ERROR_CASE(errname) \
case (errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#endif
static int ConvertReturnErrorCode(int retval) {
if (retval < 0) {
#ifdef _WIN32
switch (WSAGetLastError()) {
#else
switch (errno) {
#endif
#ifndef _WIN32 // These errorcodes don't exist in WinSock
ERROR_CASE(EPERM)
ERROR_CASE(ENOENT)
// ERROR_CASE(ESRCH)
// ERROR_CASE(EIO)
// ERROR_CASE(ENXIO)
// ERROR_CASE(E2BIG)
// ERROR_CASE(ENOEXEC)
// ERROR_CASE(EDEADLK)
ERROR_CASE(ENOMEM)
// ERROR_CASE(ECHILD)
// ERROR_CASE(EBUSY)
ERROR_CASE(EEXIST)
// ERROR_CASE(EXDEV)
ERROR_CASE(ENODEV)
// ERROR_CASE(ENOTDIR)
// ERROR_CASE(EISDIR)
ERROR_CASE(ENFILE)
// ERROR_CASE(ENOTTY)
// ERROR_CASE(ETXTBSY)
// ERROR_CASE(EFBIG)
ERROR_CASE(ENOSPC)
// ERROR_CASE(ESPIPE)
// ERROR_CASE(EROFS)
// ERROR_CASE(EMLINK)
ERROR_CASE(EPIPE)
// ERROR_CASE(EDOM)
// ERROR_CASE(ERANGE)
// ERROR_CASE(ENOLCK)
// ERROR_CASE(ENOSYS)
// ERROR_CASE(EIDRM)
// ERROR_CASE(EOVERFLOW)
// ERROR_CASE(EILSEQ)
// ERROR_CASE(ENOTSUP)
ERROR_CASE(ECANCELED)
// ERROR_CASE(EBADMSG)
ERROR_CASE(ENODATA)
// ERROR_CASE(ENOSR)
// ERROR_CASE(ENOSTR)
// ERROR_CASE(ETIME)
#endif
ERROR_CASE(EINTR)
ERROR_CASE(EBADF)
ERROR_CASE(EACCES)
ERROR_CASE(EFAULT)
ERROR_CASE(EINVAL)
ERROR_CASE(EMFILE)
ERROR_CASE(EWOULDBLOCK)
ERROR_CASE(EINPROGRESS)
ERROR_CASE(EALREADY)
ERROR_CASE(ENOTSOCK)
ERROR_CASE(EDESTADDRREQ)
ERROR_CASE(EMSGSIZE)
ERROR_CASE(EPROTOTYPE)
ERROR_CASE(ENOPROTOOPT)
ERROR_CASE(EPROTONOSUPPORT)
#if defined(__APPLE__) || defined(_WIN32)
ERROR_CASE(EOPNOTSUPP)
#endif
ERROR_CASE(EAFNOSUPPORT)
ERROR_CASE(EADDRINUSE)
ERROR_CASE(EADDRNOTAVAIL)
ERROR_CASE(ENETDOWN)
ERROR_CASE(ENETUNREACH)
ERROR_CASE(ENETRESET)
ERROR_CASE(ECONNABORTED)
ERROR_CASE(ECONNRESET)
ERROR_CASE(ENOBUFS)
ERROR_CASE(EISCONN)
ERROR_CASE(ENOTCONN)
ERROR_CASE(ETIMEDOUT)
ERROR_CASE(ECONNREFUSED)
ERROR_CASE(ELOOP)
ERROR_CASE(ENAMETOOLONG)
ERROR_CASE(EHOSTUNREACH)
ERROR_CASE(ENOTEMPTY)
}
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
return -1;
}
// if it is 0 or positive return it as it is
return retval;
}
static int ConvertLevels(int level) {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return SOL_SOCKET;
case ORBIS_NET_IPPROTO_IP:
return IPPROTO_IP;
case ORBIS_NET_IPPROTO_TCP:
return IPPROTO_TCP;
case ORBIS_NET_IPPROTO_IPV6:
return IPPROTO_IPV6;
}
return -1;
}
static void convertOrbisNetSockaddrToUnix(const OrbisNetSockaddr* src, sockaddr_un* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(sockaddr_un));
const OrbisNetSockaddrUn* src_in = (const OrbisNetSockaddrUn*)src;
sockaddr_un* dst_in = (sockaddr_un*)dst;
dst_in->sun_family = src_in->sun_family;
memcpy(&dst_in->sun_path, &src_in->sun_path, src_in->sun_len);
}
static void convertUnixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(OrbisNetSockaddrUn));
OrbisNetSockaddrUn* dst_in = (OrbisNetSockaddrUn*)dst;
sockaddr_un* src_in = (sockaddr_un*)src;
dst_in->sun_len = strnlen(src_in->sun_path, 108);
dst_in->sun_family = src_in->sun_family;
memcpy(&dst_in->sun_path, &src_in->sun_path, dst_in->sun_len);
}
bool UnixSocket::IsValid() const {
#ifdef _WIN32
return sock != INVALID_SOCKET;
#else
return sock != -1;
#endif
}
int UnixSocket::Close() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
auto out = closesocket(sock);
#else
auto out = ::close(sock);
#endif
return ConvertReturnErrorCode(out);
}
int UnixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
convertOrbisNetSockaddrToUnix(addr, &addr2);
return ConvertReturnErrorCode(::bind(sock, (const sockaddr*)&addr2, sizeof(sockaddr_un)));
}
int UnixSocket::Listen(int backlog) {
std::scoped_lock lock{m_mutex};
return ConvertReturnErrorCode(::listen(sock, backlog));
}
int UnixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
DWORD bytesSent = 0;
LPFN_WSASENDMSG wsasendmsg = nullptr;
GUID guid = WSAID_WSASENDMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
return ConvertReturnErrorCode(-1);
}
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
&bytesSent, nullptr, nullptr);
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesSent);
#else
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
return ConvertReturnErrorCode(res);
#endif
}
int UnixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "called");
if (to != nullptr) {
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(to, &addr);
return ConvertReturnErrorCode(
sendto(sock, (const char*)msg, len, flags, (sockaddr*)&addr, sizeof(sockaddr_un)));
} else {
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
}
}
int UnixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{receive_mutex};
#ifdef _WIN32
LPFN_WSARECVMSG wsarecvmsg = nullptr;
GUID guid = WSAID_WSARECVMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
return ConvertReturnErrorCode(-1);
}
DWORD bytesReceived = 0;
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesReceived);
#else
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
return ConvertReturnErrorCode(res);
#endif
}
int UnixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
std::scoped_lock lock{receive_mutex};
LOG_ERROR(Lib_Net, "called");
if (from != nullptr) {
sockaddr_un addr;
int res = recvfrom(sock, (char*)buf, len, flags, (sockaddr*)&addr, (socklen_t*)fromlen);
convertUnixSockaddrToOrbis((sockaddr*)&addr, from);
*fromlen = sizeof(OrbisNetSockaddrUn);
return ConvertReturnErrorCode(res);
} else {
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
}
}
SocketPtr UnixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
socklen_t len = sizeof(addr2);
net_socket new_socket = ::accept(sock, (sockaddr*)&addr2, &len);
#ifdef _WIN32
if (new_socket != INVALID_SOCKET) {
#else
if (new_socket >= 0) {
#endif
if (addr && addrlen) {
convertUnixSockaddrToOrbis((sockaddr*)&addr2, addr);
*addrlen = sizeof(OrbisNetSockaddrUn);
}
return std::make_shared<UnixSocket>(new_socket);
}
ConvertReturnErrorCode(new_socket);
return nullptr;
}
int UnixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
convertOrbisNetSockaddrToUnix(addr, &addr2);
int result = 0;
do {
result = ::connect(sock, (sockaddr*)&addr2, sizeof(sockaddr_un));
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
result == -1 ? Common::GetLastErrorMsg() : "none");
} while (result == -1 && (errno == EINTR || errno == EINPROGRESS));
return ConvertReturnErrorCode(result);
}
int UnixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_un);
}
int res = getsockname(sock, (sockaddr*)&addr, (socklen_t*)namelen);
if (res >= 0) {
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
*namelen = sizeof(OrbisNetSockaddrUn);
}
return ConvertReturnErrorCode(res);
}
#define CASE_SETSOCKOPT(opt) \
case ORBIS_NET_##opt: \
return ConvertReturnErrorCode( \
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
#define CASE_SETSOCKOPT_VALUE(opt, value) \
case opt: \
if (optlen != sizeof(*value)) { \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
memcpy(value, optval, optlen); \
return 0
int UnixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
return -1;
}
#define CASE_GETSOCKOPT(opt) \
case ORBIS_NET_##opt: { \
socklen_t optlen_temp = *optlen; \
auto retval = ConvertReturnErrorCode( \
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
*optlen = optlen_temp; \
return retval; \
}
#define CASE_GETSOCKOPT_VALUE(opt, value) \
case opt: \
if (*optlen < sizeof(value)) { \
*optlen = sizeof(value); \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
*optlen = sizeof(value); \
*(decltype(value)*)optval = value; \
return 0;
int UnixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
return ConvertReturnErrorCode(-1);
}
int UnixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
LOG_DEBUG(Lib_Net, "called");
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_un);
}
int res = ::getpeername(sock, (sockaddr*)&addr, (socklen_t*)namelen);
if (res >= 0) {
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
*namelen = sizeof(OrbisNetSockaddrUn);
}
return ConvertReturnErrorCode(res);
}
int UnixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
#ifdef _WIN32
LOG_ERROR(Lib_Net, "(STUBBED) called");
sb->st_mode = 0000777u | 0140000u;
return 0;
#else
struct stat st{};
int result = ::fstat(sock, &st);
sb->st_mode = 0000777u | 0140000u;
sb->st_size = st.st_size;
sb->st_blocks = st.st_blocks;
sb->st_blksize = st.st_blksize;
// sb->st_flags = st.st_flags;
return ConvertReturnErrorCode(result);
#endif
}
} // namespace Libraries::Net
+388
View File
@@ -0,0 +1,388 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_auth.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpAuth {
static bool g_shadnet_enabled = false;
static s32 g_active_auth_requests = 0;
static std::mutex g_auth_request_mutex;
// Internal types for storing request-related information
enum class NpAuthRequestState {
None = 0,
Ready = 1,
Aborted = 2,
Complete = 3,
};
struct NpAuthRequest {
NpAuthRequestState state;
bool async;
s32 result;
};
static std::vector<NpAuthRequest> g_auth_requests;
s32 CreateNpAuthRequest(bool async) {
if (g_active_auth_requests == ORBIS_NP_AUTH_REQUEST_LIMIT) {
return ORBIS_NP_AUTH_ERROR_REQUEST_MAX;
}
std::scoped_lock lk{g_auth_request_mutex};
s32 req_index = 0;
while (req_index < g_auth_requests.size()) {
// Find first nonexistant request
if (g_auth_requests[req_index].state == NpAuthRequestState::None) {
// There is no request at this index, set the index to ready then break.
g_auth_requests[req_index].state = NpAuthRequestState::Ready;
g_auth_requests[req_index].async = async;
break;
}
req_index++;
}
if (req_index == g_auth_requests.size()) {
// There are no requests to replace.
NpAuthRequest new_request{NpAuthRequestState::Ready, async, 0};
g_auth_requests.emplace_back(new_request);
}
// Offset by one, first returned ID is 0x10000001
g_active_auth_requests++;
LOG_DEBUG(Lib_NpAuth, "called, async = {}", async);
return req_index + ORBIS_NP_AUTH_REQUEST_ID_OFFSET + 1;
}
s32 PS4_SYSV_ABI sceNpAuthCreateRequest() {
return CreateNpAuthRequest(false);
}
s32 PS4_SYSV_ABI sceNpAuthCreateAsyncRequest(const OrbisNpAuthCreateAsyncRequestParameter* param) {
if (param == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (param->size != sizeof(OrbisNpAuthCreateAsyncRequestParameter)) {
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
}
return CreateNpAuthRequest(true);
}
s32 GetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
s32 flag, OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
if (param == nullptr || auth_code == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (param->size != sizeof(OrbisNpAuthGetAuthorizationCodeParameter)) {
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->user_id == -1 || param->client_id == nullptr || param->scope == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_auth_request_mutex};
// From here the actual authorization code request is performed.
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
auto& request = g_auth_requests[req_index];
if (request.state == NpAuthRequestState::Complete) {
request.result = ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
} else if (request.state == NpAuthRequestState::Aborted) {
request.result = ORBIS_NP_AUTH_ERROR_ABORTED;
return ORBIS_NP_AUTH_ERROR_ABORTED;
}
request.state = NpAuthRequestState::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_NpAuth, "(STUBBED) called, req_id = {:#x}, async = {}", req_id, request.async);
// Not sure what values are expected here, so zeroing these for now.
std::memset(auth_code, 0, sizeof(OrbisNpAuthorizationCode));
std::strncpy(auth_code->code, "AUTHCODE", 9);
if (issuer_id != nullptr) {
*issuer_id = 100;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI
sceNpAuthGetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameter* param,
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
if (param == nullptr || auth_code == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (param->size != sizeof(OrbisNpAuthGetAuthorizationCodeParameter)) {
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->online_id == nullptr || param->client_id == nullptr || param->scope == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (!g_shadnet_enabled) {
// Calls sceNpManagerIntGetUserIdByOnlineId to get a user id, returning any errors.
// This call will not succeed while signed out because games cannot retrieve an online id.
return ORBIS_NP_ERROR_USER_NOT_FOUND;
}
// For simplicity, call sceUserServiceGetInitialUser to get the user_id.
s32 user_id = 0;
ASSERT(UserService::sceUserServiceGetInitialUser(&user_id) == 0);
// Library constructs an OrbisNpAuthGetAuthorizationCodeParameterA struct,
// then uses that to call GetAuthorizationCode.
OrbisNpAuthGetAuthorizationCodeParameterA internal_params;
std::memset(&internal_params, 0, sizeof(internal_params));
internal_params.size = sizeof(internal_params);
internal_params.client_id = param->client_id;
internal_params.user_id = user_id;
internal_params.scope = param->scope;
return GetAuthorizationCode(req_id, &internal_params, 0, auth_code, issuer_id);
}
s32 PS4_SYSV_ABI
sceNpAuthGetAuthorizationCodeA(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
return GetAuthorizationCode(req_id, param, 0, auth_code, issuer_id);
}
s32 PS4_SYSV_ABI
sceNpAuthGetAuthorizationCodeV3(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParameterA* param,
OrbisNpAuthorizationCode* auth_code, s32* issuer_id) {
return GetAuthorizationCode(req_id, param, 1, auth_code, issuer_id);
}
s32 GetIdToken(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param, s32 flag,
OrbisNpIdToken* token) {
if (param == nullptr || token == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (param->size != sizeof(OrbisNpAuthGetIdTokenParameterA)) {
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->user_id == -1 || param->client_id == nullptr || param->client_secret == nullptr ||
param->scope == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_auth_request_mutex};
// From here the actual authorization code request is performed.
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
auto& request = g_auth_requests[req_index];
if (request.state == NpAuthRequestState::Complete) {
request.result = ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
} else if (request.state == NpAuthRequestState::Aborted) {
request.result = ORBIS_NP_AUTH_ERROR_ABORTED;
return ORBIS_NP_AUTH_ERROR_ABORTED;
}
request.state = NpAuthRequestState::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_NpAuth, "(STUBBED) called, req_id = {:#x}, async = {}", req_id, request.async);
// Not sure what values are expected here, so zeroing this for now.
std::memset(token, 0, sizeof(OrbisNpIdToken));
std::strncpy(token->token, "TOKEN", 6);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpAuthGetIdToken(s32 req_id, const OrbisNpAuthGetIdTokenParameter* param,
OrbisNpIdToken* token) {
if (param == nullptr || token == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (param->size != sizeof(OrbisNpAuthGetIdTokenParameter)) {
return ORBIS_NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->online_id == nullptr || param->client_id == nullptr ||
param->client_secret == nullptr || param->scope == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
if (!g_shadnet_enabled) {
// Calls sceNpManagerIntGetUserIdByOnlineId to get a user id, returning any errors.
// This call will not succeed while signed out because games cannot retrieve an online id.
return ORBIS_NP_ERROR_USER_NOT_FOUND;
}
// For simplicity, call sceUserServiceGetInitialUser to get the user_id.
s32 user_id = 0;
ASSERT(UserService::sceUserServiceGetInitialUser(&user_id) == 0);
// Library constructs an OrbisNpAuthGetIdTokenParameterA struct,
// then uses that to call GetIdToken.
OrbisNpAuthGetIdTokenParameterA internal_params;
std::memset(&internal_params, 0, sizeof(internal_params));
internal_params.size = sizeof(internal_params);
internal_params.user_id = user_id;
internal_params.client_id = param->client_id;
internal_params.client_secret = param->client_secret;
internal_params.scope = param->scope;
return GetIdToken(req_id, &internal_params, 0, token);
}
s32 PS4_SYSV_ABI sceNpAuthGetIdTokenA(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param,
OrbisNpIdToken* token) {
return GetIdToken(req_id, param, 0, token);
}
s32 PS4_SYSV_ABI sceNpAuthGetIdTokenV3(s32 req_id, const OrbisNpAuthGetIdTokenParameterA* param,
OrbisNpIdToken* token) {
return GetIdToken(req_id, param, 1, token);
}
s32 PS4_SYSV_ABI sceNpAuthSetTimeout(s32 req_id, s32 resolve_retry, u32 resolve_timeout,
u32 conn_timeout, u32 send_timeout, u32 recv_timeout) {
LOG_ERROR(Lib_NpAuth, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpAuthAbortRequest(s32 req_id) {
LOG_DEBUG(Lib_NpAuth, "called req_id = {:#x}", req_id);
std::scoped_lock lk{g_auth_request_mutex};
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
if (g_auth_requests[req_index].state == NpAuthRequestState::Complete) {
// If the request is already complete, abort is ignored.
return ORBIS_OK;
}
g_auth_requests[req_index].state = NpAuthRequestState::Aborted;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpAuthWaitAsync(s32 req_id, s32* result) {
if (result == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_auth_request_mutex};
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
if (!g_auth_requests[req_index].async ||
g_auth_requests[req_index].state == NpAuthRequestState::Ready) {
return ORBIS_NP_AUTH_ERROR_INVALID_ID;
}
// Since we're not actually performing any sort of network request here,
// we can just set result based on the request and return.
*result = g_auth_requests[req_index].result;
LOG_WARNING(Lib_NpAuth, "called req_id = {:#x}, returning result = {:#x}", req_id,
static_cast<u32>(*result));
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpAuthPollAsync(s32 req_id, s32* result) {
if (result == nullptr) {
return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_auth_request_mutex};
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
if (!g_auth_requests[req_index].async ||
g_auth_requests[req_index].state == NpAuthRequestState::Ready) {
return ORBIS_NP_AUTH_ERROR_INVALID_ID;
}
// Since we're not actually performing any sort of network request here,
// we can just set result based on the request and return.
*result = g_auth_requests[req_index].result;
LOG_WARNING(Lib_NpAuth, "called req_id = {:#x}, returning result = {:#x}", req_id,
static_cast<u32>(*result));
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 req_id) {
LOG_DEBUG(Lib_NpAuth, "called req_id = {:#x}", req_id);
std::scoped_lock lk{g_auth_request_mutex};
s32 req_index = req_id - ORBIS_NP_AUTH_REQUEST_ID_OFFSET - 1;
if (g_active_auth_requests == 0 || g_auth_requests.size() <= req_index ||
g_auth_requests[req_index].state == NpAuthRequestState::None) {
return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
g_active_auth_requests--;
g_auth_requests[req_index].state = NpAuthRequestState::None;
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
g_shadnet_enabled = EmulatorSettings.IsShadNetEnabled();
LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateRequest);
LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateAsyncRequest);
LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCode);
LIB_FUNCTION("qAUXQ9GdWp8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCodeA);
LIB_FUNCTION("KI4dHLlTNl0", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetAuthorizationCodeV3);
LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdToken);
LIB_FUNCTION("CocbHVIKPE8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdTokenA);
LIB_FUNCTION("RdsFVsgSpZY", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthGetIdTokenV3);
LIB_FUNCTION("PM3IZCw-7m0", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthSetTimeout);
LIB_FUNCTION("cE7wIsqXdZ8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthAbortRequest);
LIB_FUNCTION("SK-S7daqJSE", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthWaitAsync);
LIB_FUNCTION("gjSyfzSsDcE", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthPollAsync);
LIB_FUNCTION("H8wG9Bk-nPc", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthDeleteRequest);
LIB_FUNCTION("KxGkOrQJTqY", "libSceNpAuthCompat", 1, "libSceNpAuth",
sceNpAuthGetAuthorizationCode);
LIB_FUNCTION("uaB-LoJqHis", "libSceNpAuthCompat", 1, "libSceNpAuth", sceNpAuthGetIdToken);
};
} // namespace Libraries::Np::NpAuth
+59
View File
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpAuth {
constexpr s32 ORBIS_NP_AUTH_REQUEST_LIMIT = 0x10;
constexpr s32 ORBIS_NP_AUTH_REQUEST_ID_OFFSET = 0x10000000;
struct OrbisNpAuthCreateAsyncRequestParameter {
u64 size;
u64 cpu_affinity_mask;
s32 thread_priority;
u8 padding[4];
};
struct OrbisNpAuthGetAuthorizationCodeParameter {
u64 size;
const OrbisNpOnlineId* online_id;
const OrbisNpClientId* client_id;
const char* scope;
};
struct OrbisNpAuthGetAuthorizationCodeParameterA {
u64 size;
Libraries::UserService::OrbisUserServiceUserId user_id;
u8 padding[4];
const OrbisNpClientId* client_id;
const char* scope;
};
struct OrbisNpAuthGetIdTokenParameter {
u64 size;
const OrbisNpOnlineId* online_id;
const OrbisNpClientId* client_id;
const OrbisNpClientSecret* client_secret;
const char* scope;
};
struct OrbisNpAuthGetIdTokenParameterA {
u64 size;
Libraries::UserService::OrbisUserServiceUserId user_id;
u8 padding[4];
const OrbisNpClientId* client_id;
const OrbisNpClientSecret* client_secret;
const char* scope;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpAuth
@@ -0,0 +1,192 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <cstring>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
#include "core/libraries/system/commondialog.h"
#include "core/libraries/system/userservice.h"
#include "np_commerce.h"
namespace Libraries::Np::NpCommerce {
static Status g_dialog_status = Status::NONE;
static s32 g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
static CommerceDialogState g_state;
static CommerceDialogUi g_dialog;
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
LOG_INFO(Lib_NpCommerce, "called");
if (g_dialog_status != Status::NONE) {
return static_cast<s32>(Error::ALREADY_INITIALIZED);
}
g_dialog_status = Status::INITIALIZED;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
LOG_INFO(Lib_NpCommerce, "called");
if (g_dialog_status == Status::NONE) {
return static_cast<s32>(Error::NOT_INITIALIZED);
}
g_dialog.Finish(CommerceResult::OK);
g_dialog_status = Status::NONE;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
LOG_INFO(Lib_NpCommerce, "called");
switch (g_dialog_status) {
case Status::NONE:
return static_cast<s32>(Error::NOT_INITIALIZED);
case Status::INITIALIZED:
return static_cast<s32>(Error::NOT_RUNNING);
case Status::FINISHED:
return static_cast<s32>(Error::ALREADY_CLOSE);
default:
break;
}
g_dialog.Finish(CommerceResult::OK);
return ORBIS_OK;
}
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
return static_cast<s8>(g_dialog_status);
}
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
LOG_INFO(Lib_NpCommerce, "called");
return sceNpCommerceDialogInitialize();
}
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
return static_cast<s32>(g_dialog_status);
}
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(OrbisNpCommerceDialogResult* result) {
if (result == nullptr) {
return static_cast<s32>(Error::ARG_NULL);
}
if (g_dialog_status != Status::FINISHED) {
return static_cast<s32>(Error::NOT_FINISHED);
}
result->result = g_result_code;
// authorized is only meaningful in PLUS mode
result->authorized = g_state.mode == CommerceMode::PLUS &&
g_result_code == ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED;
result->userData = g_state.user_data;
LOG_INFO(Lib_NpCommerce, "result={} authorized={}", result->result, result->authorized);
return g_result_code;
}
s32 PS4_SYSV_ABI sceNpCommerceDialogOpen(const OrbisNpCommerceDialogParam* param) {
LOG_INFO(Lib_NpCommerce, "called, param = {}", static_cast<const void*>(param));
if (g_dialog_status != Status::INITIALIZED && g_dialog_status != Status::FINISHED) {
LOG_WARNING(Lib_NpCommerce, "Open in bad state {}", static_cast<u32>(g_dialog_status));
return static_cast<s32>(Error::NOT_INITIALIZED);
}
if (param == nullptr) {
return static_cast<s32>(Error::ARG_NULL);
}
if (param->mode < ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY ||
param->mode > ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS) {
LOG_ERROR(Lib_NpCommerce, "SceNpCommerceDialog Parameter Error (mode={})", param->mode);
g_result_code = static_cast<s32>(Error::PARAM_INVALID);
g_dialog_status = Status::FINISHED;
return static_cast<s32>(Error::PARAM_INVALID);
}
g_state = CommerceDialogState{};
g_state.mode = static_cast<CommerceMode>(param->mode);
g_state.user_id = param->userId;
g_state.service_label = param->serviceLabel;
g_state.user_data = param->userData;
// features is only meaningful in PLUS mode
g_state.features = g_state.mode == CommerceMode::PLUS ? param->features : 0;
// Resolve the calling user's name for display
char name_buf[Libraries::UserService::ORBIS_USER_SERVICE_MAX_USER_NAME_LENGTH + 1] = {};
if (Libraries::UserService::sceUserServiceGetUserName(param->userId, name_buf,
sizeof(name_buf)) >= 0) {
g_state.username = name_buf;
}
if (param->numTargets > ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX) {
LOG_WARNING(Lib_NpCommerce, "numTargets {} exceeds max {}, clamping", param->numTargets,
ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
}
const u32 count = std::min(param->numTargets, ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
if (count > 0 && param->targets != nullptr) {
g_state.targets.reserve(count);
for (u32 i = 0; i < count; ++i) {
const char* label = param->targets[i];
if (label != nullptr) {
g_state.targets.emplace_back(label, strnlen(label, MAX_LABEL_LEN));
}
}
}
LOG_INFO(Lib_NpCommerce,
"open: mode={} user_id={} serviceLabel={:#x} numTargets={} features={:#x} first='{}'",
param->mode, g_state.user_id, g_state.service_label, param->numTargets,
g_state.features, g_state.targets.empty() ? "" : g_state.targets.front());
g_dialog_status = Status::RUNNING;
g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
g_dialog = CommerceDialogUi(&g_state, &g_dialog_status, &g_result_code);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
LOG_INFO(Lib_NpCommerce, "called");
PsStoreIconLayer::Instance().Hide();
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
LOG_INFO(Lib_NpCommerce, "called, layout = {}", layout);
if (layout < 0 || layout > 2) {
LOG_WARNING(Lib_NpCommerce, "invalid layout {}, ignoring", layout);
return ORBIS_OK;
}
PsStoreIconLayer::Instance().SetLayout(static_cast<PsStoreIconLayout>(layout));
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s32 pos) {
LOG_INFO(Lib_NpCommerce, "called, pos = {}", pos);
const auto p =
(pos >= 0 && pos <= 2) ? static_cast<PsStoreIconPos>(pos) : PsStoreIconPos::CENTER;
PsStoreIconLayer::Instance().Show(p);
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogClose);
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogGetResult);
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogGetStatus);
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogInitialize);
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogInitializeInternal);
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogTerminate);
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceDialogUpdateStatus);
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceHidePsStoreIcon);
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceSetPsStoreIconLayout);
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
sceNpCommerceShowPsStoreIcon);
};
} // namespace Libraries::Np::NpCommerce
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpCommerce {
using CommonDialog::BaseParam;
using CommonDialog::Error;
using CommonDialog::Status;
static constexpr u64 MAX_LABEL_LEN = 128;
static constexpr u32 ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX = 10;
struct OrbisNpCommerceDialogParam {
BaseParam baseParam;
s32 size;
Libraries::UserService::OrbisUserServiceUserId userId;
s32 mode;
u32 serviceLabel;
const char* const* targets;
u32 numTargets;
s32 : 32;
u64 features;
void* userData;
u8 reserved[32];
};
static_assert(sizeof(OrbisNpCommerceDialogParam) == 0x80);
struct OrbisNpCommerceDialogResult {
s32 result;
bool authorized;
char : 8;
short : 16;
void* userData;
u8 reserved[32];
};
static_assert(sizeof(OrbisNpCommerceDialogResult) == 0x30);
// OrbisNpCommerceDialogResult return codes
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_OK = 0;
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED = 1;
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED = 2;
// OrbisNpCommerceDialogMode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY = 0; // Browse category mode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT = 1; // Browse product mode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT_CODE = 2; // promotion code mode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CHECKOUT = 3; // Checkout mode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_DOWNLOADLIST = 4; // Download mode
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS = 5; // PlayStation Plus mode
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpCommerce
@@ -0,0 +1,358 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <imgui.h>
#include "common/logging/log.h"
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
#include "imgui/imgui_std.h"
using namespace ImGui;
using namespace Libraries::CommonDialog;
using namespace Libraries::Np::NpCommerce;
static constexpr ImVec2 BUTTON_SIZE{160.0f, 30.0f};
static const char* ModeTitle(CommerceMode m) {
switch (m) {
case CommerceMode::CATEGORY:
return "shadNet Store";
case CommerceMode::PRODUCT:
return "Product";
case CommerceMode::PRODUCT_CODE:
return "Redeem Code";
case CommerceMode::CHECKOUT:
return "Purchase Confirmation";
case CommerceMode::DOWNLOADLIST:
return "My Downloads";
case CommerceMode::PLUS:
return "shadNet Plus";
default:
return "shadNet Store";
}
}
static const char* ModeActionLabel(CommerceMode m) {
switch (m) {
case CommerceMode::CATEGORY:
case CommerceMode::PRODUCT:
case CommerceMode::CHECKOUT:
return "Simulate purchase";
case CommerceMode::PRODUCT_CODE:
return "Redeem code";
case CommerceMode::PLUS:
return "Join SN Plus";
case CommerceMode::DOWNLOADLIST:
default:
return nullptr;
}
}
static const char* ModeEmptyText(CommerceMode m) {
switch (m) {
case CommerceMode::CATEGORY:
return "Top store category.";
case CommerceMode::PRODUCT_CODE:
return "Enter a promotion code.";
case CommerceMode::DOWNLOADLIST:
return "All downloadable content.";
case CommerceMode::PLUS:
return "Join shadNet Plus.";
default:
return "No product specified.";
}
}
static const char* ModeDescription(CommerceMode m) {
switch (m) {
case CommerceMode::CATEGORY:
return "Browsing a store category.";
case CommerceMode::PRODUCT:
return "Viewing a product.";
case CommerceMode::PRODUCT_CODE:
return "Redeeming a promotion code.";
case CommerceMode::CHECKOUT:
return "Confirming a purchase.";
case CommerceMode::DOWNLOADLIST:
return "Viewing owned downloads.";
case CommerceMode::PLUS:
return "Joining shadNet Plus.";
default:
return "";
}
}
static std::string DescribeFeatures(u64 features) {
if (features == 0) {
return "none";
}
std::string out;
if (features & 0x1) { // ORBIS_NP_PLUS_FEATURE_REALTIME_MULTIPLAY
out += "Realtime Multiplay";
}
if (out.empty()) {
return "unknown";
}
return out;
}
// Decomposed CHECKOUT/DOWNLOADLIST element "[serviceLabel:]product[-sku]".
struct ParsedTarget {
std::string service;
std::string product;
std::string sku;
};
static ParsedTarget ParseTarget(const std::string& t) {
ParsedTarget out;
std::string s = t;
if (const auto colon = s.find(':'); colon != std::string::npos) {
out.service = s.substr(0, colon);
s = s.substr(colon + 1);
}
if (const auto dash = s.find('-'); dash != std::string::npos) {
out.product = s.substr(0, dash);
out.sku = s.substr(dash + 1);
} else {
out.product = s;
}
return out;
}
CommerceDialogUi::CommerceDialogUi(CommerceDialogState* state, Status* status, s32* result)
: state(state), status(status), result(result) {
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
CommerceDialogUi::~CommerceDialogUi() {
Finish(CommerceResult::USER_CANCELED);
}
CommerceDialogUi::CommerceDialogUi(CommerceDialogUi&& other) noexcept
: Layer(other), first_render(other.first_render), state(other.state), status(other.status),
result(other.result) {
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
}
CommerceDialogUi& CommerceDialogUi::operator=(CommerceDialogUi&& other) noexcept {
if (this != &other) {
first_render = other.first_render;
state = other.state;
status = other.status;
result = other.result;
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
return *this;
}
void CommerceDialogUi::Finish(CommerceResult r) {
if (result) {
*result = static_cast<s32>(r);
}
if (status) {
*status = Status::FINISHED;
}
state = nullptr;
status = nullptr;
result = nullptr;
RemoveLayer(this);
}
void CommerceDialogUi::Draw() {
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
return;
}
const auto& io = GetIO();
const ImVec2 window_size{std::min(io.DisplaySize.x, 520.0f),
std::min(io.DisplaySize.y, 380.0f)};
CentralizeNextWindow();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
KeepNavHighlight();
if (Begin("CommerceDialog##NpCommerceDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
std::string title = ModeTitle(state->mode);
if (state->targets.size() == 1 &&
(state->mode == CommerceMode::PRODUCT || state->mode == CommerceMode::CATEGORY)) {
title += ": " + state->targets.front();
}
{
const float tw = CalcTextSize(title.c_str()).x;
SetCursorPosX(std::max(10.0f, (GetWindowSize().x - tw) * 0.5f));
TextUnformatted(title.c_str());
}
Separator();
TextWrapped("%s", ModeDescription(state->mode));
Spacing();
// Calling user.
if (!state->username.empty()) {
Text("User: %s (id %d)", state->username.c_str(), state->user_id);
} else {
Text("User id: %d", state->user_id);
}
// Service label is meaningful for category/product/checkout/downloadlist.
if (state->mode != CommerceMode::PRODUCT_CODE && state->mode != CommerceMode::PLUS) {
Text("Service label: %u", state->service_label);
}
// PS Plus feature only applies in PLUS mode.
if (state->mode == CommerceMode::PLUS) {
Text("PS Plus feature: %s", DescribeFeatures(state->features).c_str());
}
Spacing();
if (state->targets.empty()) {
TextWrapped("%s", ModeEmptyText(state->mode));
} else {
const bool checkout_form =
state->mode == CommerceMode::CHECKOUT || state->mode == CommerceMode::DOWNLOADLIST;
TextDisabled(state->targets.size() == 1 ? "Target:" : "Targets:");
for (const auto& t : state->targets) {
if (checkout_form) {
const ParsedTarget pt = ParseTarget(t);
if (!pt.sku.empty() || !pt.service.empty()) {
BulletText("Product %s", pt.product.c_str());
if (!pt.sku.empty()) {
Indent();
Text("SKU: %s", pt.sku.c_str());
Unindent();
}
if (!pt.service.empty()) {
Indent();
Text("Service: %s", pt.service.c_str());
Unindent();
}
continue;
}
}
BulletText("%s", t.c_str());
}
}
Spacing();
const char* action = ModeActionLabel(state->mode);
const auto ws = GetWindowSize();
const float n = action ? 2.0f : 1.0f;
SetCursorPos({ws.x / 2.0f - (BUTTON_SIZE.x * n + 10.0f * (n - 1.0f)) / 2.0f,
ws.y - 10.0f - BUTTON_SIZE.y});
BeginGroup();
if (action) {
if (Button(action, BUTTON_SIZE)) {
LOG_INFO(Lib_NpCommerce, "CommerceDialog: action '{}' mode={} target='{}'", action,
static_cast<s32>(state->mode),
state->targets.empty() ? "" : state->targets.front());
Finish(CommerceResult::PURCHASED);
}
SameLine();
}
if (Button("Close", BUTTON_SIZE)) {
Finish(CommerceResult::USER_CANCELED);
}
// Default focus on Close so a stray confirm press dismisses, never buys.
if (first_render) {
SetItemCurrentNavFocus();
}
EndGroup();
}
End();
first_render = false;
}
static float LayoutScale(PsStoreIconLayout l) {
switch (l) {
case PsStoreIconLayout::FIXED_90:
return 0.90f;
case PsStoreIconLayout::FOLLOW_SAFE_AREA:
return 0.95f;
case PsStoreIconLayout::DEFAULT:
default:
return 1.0f;
}
}
PsStoreIconLayer& PsStoreIconLayer::Instance() {
static PsStoreIconLayer instance;
return instance;
}
void PsStoreIconLayer::Show(PsStoreIconPos p) {
pos.store(p);
if (!shown.exchange(true)) {
AddLayer(this);
}
}
void PsStoreIconLayer::Hide() {
if (shown.exchange(false)) {
RemoveLayer(this);
}
}
void PsStoreIconLayer::SetLayout(PsStoreIconLayout l) {
layout.store(l);
}
void PsStoreIconLayer::Draw() {
if (!shown.load()) {
return;
}
const auto& io = GetIO();
const ImVec2 ds = io.DisplaySize;
const float scale = LayoutScale(layout.load());
const float inset_x = ds.x * (1.0f - scale) * 0.5f;
const float inset_y = ds.y * (1.0f - scale) * 0.5f;
constexpr ImVec2 icon{52.0f, 52.0f};
constexpr float pad = 16.0f;
float x;
switch (pos.load()) {
case PsStoreIconPos::LEFT:
x = inset_x + pad;
break;
case PsStoreIconPos::RIGHT:
x = ds.x - inset_x - icon.x - pad;
break;
case PsStoreIconPos::CENTER:
default:
x = (ds.x - icon.x) * 0.5f;
break;
}
const float y = ds.y - inset_y - icon.y - pad;
SetNextWindowPos({x, y});
SetNextWindowSize(icon);
if (Begin("##PsStoreIcon", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
auto* dl = GetWindowDrawList();
const ImVec2 p0 = GetWindowPos();
const ImVec2 p1{p0.x + icon.x, p0.y + icon.y};
dl->AddRectFilled(p0, p1, IM_COL32(0, 55, 145, 230), 10.0f);
dl->AddRect(p0, p1, IM_COL32(255, 255, 255, 210), 10.0f, 0, 2.0f);
const char* label = "SN";
const ImVec2 ts = CalcTextSize(label);
dl->AddText({p0.x + (icon.x - ts.x) * 0.5f, p0.y + (icon.y - ts.y) * 0.5f},
IM_COL32(255, 255, 255, 255), label);
}
End();
}
@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <string>
#include <vector>
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
namespace Libraries::Np::NpCommerce {
// OrbisNpCommerceDialogMode
enum class CommerceMode : s32 {
CATEGORY = 0,
PRODUCT = 1,
PRODUCT_CODE = 2,
CHECKOUT = 3,
DOWNLOADLIST = 4,
PLUS = 5,
};
// OrbisNpCommerceDialogResult result values
enum class CommerceResult : s32 {
OK = 0,
USER_CANCELED = 1,
PURCHASED = 2,
};
struct CommerceDialogState {
std::vector<std::string> targets; // product / category labels
std::string username; // resolved from userId, may be empty
CommerceMode mode{CommerceMode::CATEGORY};
s32 user_id{-1};
u32 service_label{0};
u64 features{0}; // ORBIS_NP_PLUS_FEATURE_* (PLUS mode only)
void* user_data{nullptr}; // echoed back into the result struct
};
class CommerceDialogUi final : public ImGui::Layer {
bool first_render{false};
CommerceDialogState* state{};
CommonDialog::Status* status{};
s32* result{};
public:
explicit CommerceDialogUi(CommerceDialogState* state = nullptr,
CommonDialog::Status* status = nullptr, s32* result = nullptr);
~CommerceDialogUi() override;
CommerceDialogUi(const CommerceDialogUi&) = delete;
CommerceDialogUi& operator=(const CommerceDialogUi&) = delete;
CommerceDialogUi(CommerceDialogUi&& other) noexcept;
CommerceDialogUi& operator=(CommerceDialogUi&& other) noexcept;
void Finish(CommerceResult r);
void Draw() override;
};
// OrbisNpCommercePsStoreIconPos
enum class PsStoreIconPos : s32 {
CENTER = 0, // screen lower center
LEFT = 1, // screen lower left
RIGHT = 2, // screen lower right
};
// OrbisNpCommercePsStoreIconLayout
enum class PsStoreIconLayout : s32 {
DEFAULT = 0, // full screen (100%)
FOLLOW_SAFE_AREA = 1, // follow display safe area (90%-100%)
FIXED_90 = 2, // fixed 90% of screen
};
class PsStoreIconLayer final : public ImGui::Layer {
std::atomic<bool> shown{false};
std::atomic<PsStoreIconPos> pos{PsStoreIconPos::CENTER};
std::atomic<PsStoreIconLayout> layout{PsStoreIconLayout::DEFAULT};
public:
static PsStoreIconLayer& Instance();
void Show(PsStoreIconPos p);
void Hide();
void SetLayout(PsStoreIconLayout l);
void Draw() override;
};
} // namespace Libraries::Np::NpCommerce
+617
View File
@@ -0,0 +1,617 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cctype>
#include <cstddef>
#include <cstring>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/process.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_types.h"
namespace Libraries::Np::NpCommon {
namespace {
static_assert(sizeof(OrbisNpCalloutEntry) == 0x20);
static_assert(offsetof(OrbisNpCalloutContext, thread) == 0x00);
static_assert(offsetof(OrbisNpCalloutContext, mutex) == 0x08);
static_assert(offsetof(OrbisNpCalloutContext, cond) == 0x10);
static_assert(offsetof(OrbisNpCalloutContext, active) == 0x18);
static_assert(offsetof(OrbisNpCalloutContext, stop_requested) == 0x1c);
static_assert(offsetof(OrbisNpCalloutContext, head) == 0x20);
static_assert(sizeof(OrbisNpCalloutContext) == 0x28);
s32 NormalizeNpCommonResult(int rc) {
if (rc <= 0) {
return rc;
}
return Libraries::Kernel::ErrnoToSceKernelError(rc);
}
u32 NormalizeNpTryLockResult(int rc) {
const s32 sce_rc = NormalizeNpCommonResult(rc);
if (sce_rc == ORBIS_KERNEL_ERROR_EBUSY) {
return ORBIS_NP_LW_MUTEX_ERROR_BUSY;
}
return static_cast<u32>(sce_rc);
}
void* PS4_SYSV_ABI NpCalloutThreadMain(void* arg) {
auto* ctx = static_cast<OrbisNpCalloutContext*>(arg);
const auto mutex = &ctx->mutex;
sceNpMutexLock(mutex);
for (;;) {
s64 now_usec = 0;
sceNpGetSystemClockUsec(&now_usec);
auto* entry = ctx->head;
u32 wait_usec = 0;
if (entry == nullptr) {
if (ctx->stop_requested > 0) {
break;
}
} else {
const u64 deadline = static_cast<u64>(entry->deadline_usec);
const u64 now = static_cast<u64>(now_usec);
const u64 remaining = deadline - now;
if (deadline < now || remaining == 0) {
ctx->head = entry->next;
sceNpMutexUnlock(mutex);
if (entry->handler != nullptr) {
entry->handler(entry->arg);
}
sceNpMutexLock(mutex);
continue;
}
if (ctx->stop_requested > 0) {
break;
}
wait_usec = static_cast<u32>(remaining > 0xffffffffULL ? 0xffffffffULL : remaining);
}
sceNpCondTimedwait(&ctx->cond, mutex, wait_usec);
if (ctx->stop_requested > 0) {
break;
}
}
sceNpMutexUnlock(mutex);
return nullptr;
}
} // namespace
s32 PS4_SYSV_ABI sceNpCmpNpId(OrbisNpId* np_id1, OrbisNpId* np_id2) {
if (np_id1 == nullptr || np_id2 == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
if (std::strncmp(np_id1->handle.data, np_id2->handle.data, ORBIS_NP_ONLINEID_MAX_LENGTH) != 0) {
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
}
for (u32 i = 0; i < 8; i++) {
if (np_id1->opt[i] != np_id2->opt[i]) {
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
}
}
for (u32 i = 0; i < 8; i++) {
if (np_id1->reserved[i] != np_id2->reserved[i]) {
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
}
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCmpNpIdInOrder(OrbisNpId* np_id1, OrbisNpId* np_id2, u32* out_result) {
if (np_id1 == nullptr || np_id2 == nullptr || out_result == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
u32 compare =
std::strncmp(np_id1->handle.data, np_id2->handle.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
if (compare < 0) {
*out_result = -1;
return ORBIS_OK;
} else if (compare > 0) {
*out_result = 1;
return ORBIS_OK;
}
for (u32 i = 0; i < 8; i++) {
if (np_id1->opt[i] < np_id2->opt[i]) {
*out_result = -1;
return ORBIS_OK;
} else if (np_id1->opt[i] > np_id2->opt[i]) {
*out_result = 1;
return ORBIS_OK;
}
}
for (u32 i = 0; i < 8; i++) {
if (np_id1->reserved[i] < np_id2->reserved[i]) {
*out_result = -1;
return ORBIS_OK;
} else if (np_id1->reserved[i] > np_id2->reserved[i]) {
*out_result = 1;
return ORBIS_OK;
}
}
*out_result = 0;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCmpOnlineId(OrbisNpOnlineId* online_id1, OrbisNpOnlineId* online_id2) {
if (online_id1 == nullptr || online_id2 == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
if (std::strncmp(online_id1->data, online_id2->data, ORBIS_NP_ONLINEID_MAX_LENGTH) != 0) {
return ORBIS_NP_UTIL_ERROR_NOT_MATCH;
}
return ORBIS_OK;
}
u32 PS4_SYSV_ABI sceNpMutexLock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return static_cast<u32>(
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_lock(mutex)));
}
u32 PS4_SYSV_ABI sceNpMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return static_cast<u32>(
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_unlock(mutex)));
}
u32 PS4_SYSV_ABI sceNpMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
u64 flags) {
LOG_DEBUG(Lib_NpCommon, "mutex={:p} name={:p} flags={:#x}", fmt::ptr(mutex), fmt::ptr(name),
flags);
Libraries::Kernel::PthreadMutexAttrT attr = nullptr;
int rc = Libraries::Kernel::posix_pthread_mutexattr_init(&attr);
if (rc == 0) {
if ((flags & 1) != 0) {
rc = Libraries::Kernel::posix_pthread_mutexattr_settype(
&attr, Libraries::Kernel::PthreadMutexType::Recursive);
}
if (rc == 0) {
rc = Libraries::Kernel::scePthreadMutexInit(mutex, &attr, name);
}
Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
}
return static_cast<u32>(NormalizeNpCommonResult(rc));
}
s32 PS4_SYSV_ABI sceNpMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_DEBUG(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_mutex_destroy(mutex));
}
u32 PS4_SYSV_ABI sceNpMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return NormalizeNpTryLockResult(Libraries::Kernel::posix_pthread_mutex_trylock(mutex));
}
u32 PS4_SYSV_ABI sceNpLwMutexLock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return sceNpMutexLock(mutex);
}
u32 PS4_SYSV_ABI sceNpLwMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return sceNpMutexUnlock(mutex);
}
u32 PS4_SYSV_ABI sceNpLwMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
u64 flags) {
LOG_DEBUG(Lib_NpCommon, "mutex={:p} name={:p} flags={:#x}", fmt::ptr(mutex), fmt::ptr(name),
flags);
return sceNpMutexInit(mutex, name, flags);
}
s32 PS4_SYSV_ABI sceNpLwMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_DEBUG(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return sceNpMutexDestroy(mutex);
}
u32 PS4_SYSV_ABI sceNpLwMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex) {
LOG_TRACE(Lib_NpCommon, "mutex={:p}", fmt::ptr(mutex));
return sceNpMutexTryLock(mutex);
}
s32 PS4_SYSV_ABI sceNpCondInit(Libraries::Kernel::PthreadCondT* cond, const char* name, u64 flags) {
LOG_DEBUG(Lib_NpCommon, "cond={:p} name={:p} flags={:#x}", fmt::ptr(cond), fmt::ptr(name),
flags);
Libraries::Kernel::PthreadCondAttrT attr = nullptr;
int rc = Libraries::Kernel::posix_pthread_condattr_init(&attr);
if (rc == 0) {
rc = Libraries::Kernel::scePthreadCondInit(cond, &attr, name);
Libraries::Kernel::posix_pthread_condattr_destroy(&attr);
}
return NormalizeNpCommonResult(rc);
}
s32 PS4_SYSV_ABI sceNpCondDestroy(Libraries::Kernel::PthreadCondT* cond) {
LOG_DEBUG(Lib_NpCommon, "cond={:p}", fmt::ptr(cond));
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_destroy(cond));
}
s32 PS4_SYSV_ABI sceNpCondSignal(Libraries::Kernel::PthreadCondT* cond) {
LOG_DEBUG(Lib_NpCommon, "cond={:p}", fmt::ptr(cond));
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_signal(cond));
}
s32 PS4_SYSV_ABI sceNpCondTimedwait(Libraries::Kernel::PthreadCondT* cond,
Libraries::Kernel::PthreadMutexT* mutex, u32 usec) {
LOG_DEBUG(Lib_NpCommon, "cond={:p} mutex={:p} usec={:#x}", fmt::ptr(cond), fmt::ptr(mutex),
usec);
if (usec == 0) {
return NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_cond_wait(cond, mutex));
}
const s32 rc = NormalizeNpCommonResult(
Libraries::Kernel::posix_pthread_cond_reltimedwait_np(cond, mutex, usec));
if (rc == ORBIS_KERNEL_ERROR_ETIMEDOUT) {
return ORBIS_NP_LW_COND_ERROR_TIMEDOUT;
}
return rc;
}
s32 PS4_SYSV_ABI sceNpCreateThread(Libraries::Kernel::PthreadT* thread, NpThreadEntry start_routine,
void* arg, s32 priority, u64 stack_size, u64 affinity_mask,
const char* name) {
LOG_DEBUG(Lib_NpCommon,
"thread={:p} start={:#x} arg={:p} priority={} stack={:#x} "
"affinity={:#x} name={:p}",
fmt::ptr(thread), reinterpret_cast<u64>(start_routine), fmt::ptr(arg), priority,
stack_size, affinity_mask, fmt::ptr(name));
Libraries::Kernel::PthreadAttrT attr = nullptr;
int rc = Libraries::Kernel::posix_pthread_attr_init(&attr);
if (rc != 0) {
return NormalizeNpCommonResult(rc);
}
rc = Libraries::Kernel::posix_pthread_attr_setstacksize(&attr, static_cast<size_t>(stack_size));
if (rc == 0) {
if (priority != 0) {
s32 sdk_version = 0;
rc = Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_version);
if (rc >= 0 && sdk_version >= 0x2500000) {
rc = Libraries::Kernel::posix_pthread_attr_setinheritsched(&attr, 0);
if (rc == 0) {
rc = Libraries::Kernel::posix_pthread_attr_setschedpolicy(
&attr, Libraries::Kernel::SchedPolicy::Fifo);
}
}
if (rc >= 0) {
Libraries::Kernel::SchedParam param{.sched_priority = priority};
rc = Libraries::Kernel::posix_pthread_attr_setschedparam(&attr, &param);
}
}
if (rc == 0 && affinity_mask != 0) {
rc = Libraries::Kernel::scePthreadAttrSetaffinity(&attr, affinity_mask);
}
if (rc == 0) {
rc = Libraries::Kernel::posix_pthread_create_name_np(thread, &attr, start_routine, arg,
name);
}
}
Libraries::Kernel::posix_pthread_attr_destroy(&attr);
return NormalizeNpCommonResult(rc);
}
u32 PS4_SYSV_ABI sceNpJoinThread(Libraries::Kernel::PthreadT thread, void** ret) {
LOG_DEBUG(Lib_NpCommon, "thread={:p} ret={:p}", fmt::ptr(thread), fmt::ptr(ret));
return static_cast<u32>(
NormalizeNpCommonResult(Libraries::Kernel::posix_pthread_join(thread, ret)));
}
s32 PS4_SYSV_ABI sceNpGetSystemClockUsec(s64* usec) {
LOG_DEBUG(Lib_NpCommon, "usec={:p}", fmt::ptr(usec));
Libraries::Kernel::OrbisKernelTimespec ts{};
const s32 rc =
Libraries::Kernel::sceKernelClockGettime(Libraries::Kernel::ORBIS_CLOCK_MONOTONIC, &ts);
if (rc == ORBIS_OK) {
*usec = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
return rc;
}
s32 PS4_SYSV_ABI sceNpGetPlatformType(const OrbisNpId* npid) {
LOG_DEBUG(Lib_NpCommon, "np_id={:p}", fmt::ptr(npid));
if (npid == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
static constexpr u32 kTagPs3 = 0x00337370; // "ps3"
static constexpr u32 kTagVita = 0x32707370; // "psp2(VITA)"
static constexpr u32 kTagPs4 = 0x00347370; // "ps4"
if (npid->opt[4] == 0) {
return OrbisNpPlatformType::None;
}
u32 tag = 0;
std::memcpy(&tag, npid->opt + 4, sizeof(tag));
switch (tag) {
case kTagPs3:
return OrbisNpPlatformType::PS3;
case kTagVita:
return OrbisNpPlatformType::Vita;
case kTagPs4:
return OrbisNpPlatformType::PS4;
default:
return ORBIS_NP_ERROR_INVALID_PLATFORM_TYPE;
}
}
s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id) {
LOG_DEBUG(Lib_NpCommon, "online_id={:p}", fmt::ptr(id));
if (id == nullptr || id->term != 0) {
return 0;
}
const size_t len = strnlen(id->data, ORBIS_NP_ONLINEID_MAX_LENGTH + 1);
if (len < 3 || len > ORBIS_NP_ONLINEID_MAX_LENGTH) {
return 0;
}
const unsigned char first = static_cast<unsigned char>(id->data[0]);
if (!std::isalnum(first)) {
return 0;
}
for (size_t i = 1; i < len; ++i) {
const unsigned char c = static_cast<unsigned char>(id->data[i]);
if (std::isalnum(c) || c == '_' || c == '-') {
continue;
}
return 0;
}
return 1;
}
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf) {
LOG_DEBUG(Lib_NpCommon, "called");
// Real library has no parameter checks, so I guess this is purely for internal use?
Libraries::Kernel::SwVersionStruct sw_version{};
sw_version.struct_size = 0x28;
Libraries::Kernel::sceKernelGetSystemSwVersion(&sw_version);
u32 sw_hex = sw_version.hex_representation;
snprintf(version_buf, 8, "%d.%02d", ((sw_hex >> 0x18) & 0xf) + ((sw_hex >> 0x1c) * 10),
((sw_hex >> 0x10) & 0xf) + (((sw_hex >> 0x14) & 0xf) * 10));
}
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* callout_ctx, const char* name,
u64 stack_size, s32 priority, u64 affinity_mask) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p} name={:p} stack={:#x} priority={} affinity={:#x}",
fmt::ptr(callout_ctx), fmt::ptr(name), stack_size, priority, affinity_mask);
s32 rc = ORBIS_NP_CALLOUT_ERROR_ALREADY_INITIALIZED;
if (callout_ctx->active < 1) {
callout_ctx->stop_requested = 0;
callout_ctx->head = nullptr;
rc = static_cast<s32>(sceNpMutexInit(&callout_ctx->mutex, name, 1));
if (rc >= 0) {
rc = sceNpCondInit(&callout_ctx->cond, name, 0);
if (rc >= 0) {
rc = sceNpCreateThread(&callout_ctx->thread, NpCalloutThreadMain, callout_ctx,
priority, stack_size, affinity_mask, name);
if (rc >= 0) {
callout_ctx->active = 1;
return ORBIS_OK;
}
sceNpCondDestroy(&callout_ctx->cond);
}
sceNpMutexDestroy(&callout_ctx->mutex);
}
}
return rc;
}
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx(OrbisNpCalloutContext* callout_ctx,
OrbisNpCalloutEntry* entry, u64 delay_usec, u64 handler,
u64 arg) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} delay={:#x} handler={:p} arg={:#x}",
fmt::ptr(callout_ctx), fmt::ptr(entry), delay_usec,
fmt::ptr(reinterpret_cast<void*>(static_cast<uintptr_t>(handler))), arg);
if (callout_ctx->active == 0) {
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
}
entry->next = nullptr;
entry->handler = reinterpret_cast<OrbisNpCalloutHandler>(handler);
entry->arg = arg;
sceNpGetSystemClockUsec(&entry->deadline_usec);
entry->deadline_usec += static_cast<s64>(static_cast<u32>(delay_usec));
sceNpMutexLock(&callout_ctx->mutex);
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
if (cur == entry) {
sceNpMutexUnlock(&callout_ctx->mutex);
return ORBIS_NP_CALLOUT_ERROR_DUPLICATE_ENTRY;
}
}
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
if (cur->deadline_usec >= entry->deadline_usec) {
break;
}
prev_next = &cur->next;
}
entry->next = *prev_next;
*prev_next = entry;
sceNpCondSignal(&callout_ctx->cond);
sceNpMutexUnlock(&callout_ctx->mutex);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx64(OrbisNpCalloutContext* callout_ctx,
OrbisNpCalloutEntry* entry, s64 delay_usec, u64 handler,
u64 arg) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} delay={} handler={:p} arg={:#x}",
fmt::ptr(callout_ctx), fmt::ptr(entry), delay_usec,
fmt::ptr(reinterpret_cast<void*>(static_cast<uintptr_t>(handler))), arg);
if (callout_ctx->active == 0) {
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
}
entry->next = nullptr;
entry->handler = reinterpret_cast<OrbisNpCalloutHandler>(handler);
entry->arg = arg;
sceNpGetSystemClockUsec(&entry->deadline_usec);
entry->deadline_usec += delay_usec;
sceNpMutexLock(&callout_ctx->mutex);
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
if (cur == entry) {
sceNpMutexUnlock(&callout_ctx->mutex);
return ORBIS_NP_CALLOUT_ERROR_DUPLICATE_ENTRY;
}
}
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
if (cur->deadline_usec >= entry->deadline_usec) {
break;
}
prev_next = &cur->next;
}
entry->next = *prev_next;
*prev_next = entry;
sceNpCondSignal(&callout_ctx->cond);
sceNpMutexUnlock(&callout_ctx->mutex);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpCalloutStopOnCtx(OrbisNpCalloutContext* callout_ctx,
OrbisNpCalloutEntry* entry, u32* removed) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p} callout={:p} removed={:p}", fmt::ptr(callout_ctx),
fmt::ptr(entry), fmt::ptr(removed));
if (callout_ctx->active == 0) {
return ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED;
}
sceNpMutexLock(&callout_ctx->mutex);
u32 found = 0;
OrbisNpCalloutEntry** prev_next = &callout_ctx->head;
for (auto* cur = callout_ctx->head; cur != nullptr; cur = cur->next) {
if (cur == entry) {
*prev_next = cur->next;
found = 1;
break;
}
prev_next = &cur->next;
}
if (removed != nullptr) {
*removed = found;
}
sceNpMutexUnlock(&callout_ctx->mutex);
return ORBIS_OK;
}
void PS4_SYSV_ABI sceNpCalloutTermCtx(OrbisNpCalloutContext* callout_ctx) {
LOG_DEBUG(Lib_NpCommon, "ctx={:p}", fmt::ptr(callout_ctx));
if (callout_ctx->active != 0) {
sceNpMutexLock(&callout_ctx->mutex);
callout_ctx->stop_requested = 1;
sceNpCondSignal(&callout_ctx->cond);
sceNpMutexUnlock(&callout_ctx->mutex);
sceNpJoinThread(callout_ctx->thread, nullptr);
sceNpCondDestroy(&callout_ctx->cond);
sceNpMutexDestroy(&callout_ctx->mutex);
callout_ctx->active = 0;
}
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("i8UmXTSq7N4", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpNpId);
LIB_FUNCTION("TcwEFnakiSc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpNpIdInOrder);
LIB_FUNCTION("dj+O5aD2a0Q", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCmpOnlineId);
LIB_FUNCTION("1a+iY5YUJcI", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondDestroy);
LIB_FUNCTION("9+m5nRdJ-wQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutInitCtx);
LIB_FUNCTION("AqJ4xkWsV+I", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutTermCtx);
LIB_FUNCTION("18j+qk6dRwk", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexLock);
LIB_FUNCTION("1CiXI-MyEKs", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexInit);
LIB_FUNCTION("4zxevggtYrQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexDestroy);
LIB_FUNCTION("CQG2oyx1-nM", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexUnlock);
LIB_FUNCTION("DuslmoqQ+nk", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexTryLock);
LIB_FUNCTION("EjMsfO3GCIA", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpJoinThread);
LIB_FUNCTION("fClnlkZmA6k", "libSceNpCommonCompat", 1, "libSceNpCommon",
sceNpCalloutStartOnCtx);
LIB_FUNCTION("fhJ5uKzcn0w", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCreateThread);
LIB_FUNCTION("hkeX9iuCwlI", "libSceNpCommonCompat", 1, "libSceNpCommon",
sceNpIntIsValidOnlineId);
LIB_FUNCTION("hp0kVgu5Fxw", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpLwMutexTryLock);
LIB_FUNCTION("in19gH7G040", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCalloutStopOnCtx);
LIB_FUNCTION("lQ11BpMM4LU", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexDestroy);
LIB_FUNCTION("lpr66Gby8dQ", "libSceNpCommonCompat", 1, "libSceNpCommon",
sceNpCalloutStartOnCtx64);
LIB_FUNCTION("oZyb9ktuCpA", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexUnlock);
LIB_FUNCTION("PVVsRmMkO1g", "libSceNpCommonCompat", 1, "libSceNpCommon",
sceNpGetSystemClockUsec);
LIB_FUNCTION("q2tsVO3lM4A", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondInit);
LIB_FUNCTION("r9Bet+s6fKc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexLock);
LIB_FUNCTION("sXVQUIGmk2U", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpGetPlatformType);
LIB_FUNCTION("ss2xO9IJxKQ", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondTimedwait);
LIB_FUNCTION("uEwag-0YZPc", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpMutexInit);
LIB_FUNCTION("uMJFOA62mVU", "libSceNpCommonCompat", 1, "libSceNpCommon", sceNpCondSignal);
LIB_FUNCTION("i8UmXTSq7N4", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpNpId);
LIB_FUNCTION("TcwEFnakiSc", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpNpIdInOrder);
LIB_FUNCTION("dj+O5aD2a0Q", "libSceNpCommon", 1, "libSceNpCommon", sceNpCmpOnlineId);
LIB_FUNCTION("1a+iY5YUJcI", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondDestroy);
LIB_FUNCTION("9+m5nRdJ-wQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutInitCtx);
LIB_FUNCTION("AqJ4xkWsV+I", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutTermCtx);
LIB_FUNCTION("18j+qk6dRwk", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexLock);
LIB_FUNCTION("1CiXI-MyEKs", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexInit);
LIB_FUNCTION("4zxevggtYrQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexDestroy);
LIB_FUNCTION("CQG2oyx1-nM", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexUnlock);
LIB_FUNCTION("DuslmoqQ+nk", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexTryLock);
LIB_FUNCTION("EjMsfO3GCIA", "libSceNpCommon", 1, "libSceNpCommon", sceNpJoinThread);
LIB_FUNCTION("fClnlkZmA6k", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStartOnCtx);
LIB_FUNCTION("fhJ5uKzcn0w", "libSceNpCommon", 1, "libSceNpCommon", sceNpCreateThread);
LIB_FUNCTION("hkeX9iuCwlI", "libSceNpCommon", 1, "libSceNpCommon", sceNpIntIsValidOnlineId);
LIB_FUNCTION("hp0kVgu5Fxw", "libSceNpCommon", 1, "libSceNpCommon", sceNpLwMutexTryLock);
LIB_FUNCTION("in19gH7G040", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStopOnCtx);
LIB_FUNCTION("lQ11BpMM4LU", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexDestroy);
LIB_FUNCTION("lpr66Gby8dQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCalloutStartOnCtx64);
LIB_FUNCTION("oZyb9ktuCpA", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexUnlock);
LIB_FUNCTION("PVVsRmMkO1g", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetSystemClockUsec);
LIB_FUNCTION("q2tsVO3lM4A", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondInit);
LIB_FUNCTION("r9Bet+s6fKc", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexLock);
LIB_FUNCTION("sXVQUIGmk2U", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetPlatformType);
LIB_FUNCTION("ss2xO9IJxKQ", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondTimedwait);
LIB_FUNCTION("uEwag-0YZPc", "libSceNpCommon", 1, "libSceNpCommon", sceNpMutexInit);
LIB_FUNCTION("uMJFOA62mVU", "libSceNpCommon", 1, "libSceNpCommon", sceNpCondSignal);
LIB_FUNCTION("Pglk7zFj0DI", "libSceNpCommon", 1, "libSceNpCommon", sceNpGetSdkVersion);
};
} // namespace Libraries::Np::NpCommon
+77
View File
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/np/np_types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpCommon {
struct OrbisNpFreeKernelMemoryArgs {
u64 length;
u64 unk;
void* addr;
};
using NpThreadEntry = void* PS4_SYSV_ABI (*)(void*);
using OrbisNpCalloutHandler = void PS4_SYSV_ABI (*)(u64);
struct OrbisNpCalloutEntry {
OrbisNpCalloutEntry* next;
OrbisNpCalloutHandler handler;
s64 deadline_usec;
u64 arg;
};
struct OrbisNpCalloutContext {
Libraries::Kernel::PthreadT thread;
Libraries::Kernel::PthreadMutexT mutex;
Libraries::Kernel::PthreadCondT cond;
s32 active;
s32 stop_requested;
OrbisNpCalloutEntry* head;
};
u32 PS4_SYSV_ABI sceNpMutexLock(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
u64 flags);
s32 PS4_SYSV_ABI sceNpMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpLwMutexLock(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpLwMutexUnlock(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpLwMutexInit(Libraries::Kernel::PthreadMutexT* mutex, const char* name,
u64 flags);
s32 PS4_SYSV_ABI sceNpLwMutexDestroy(Libraries::Kernel::PthreadMutexT* mutex);
u32 PS4_SYSV_ABI sceNpLwMutexTryLock(Libraries::Kernel::PthreadMutexT* mutex);
s32 PS4_SYSV_ABI sceNpCondInit(Libraries::Kernel::PthreadCondT* cond, const char* name, u64 flags);
s32 PS4_SYSV_ABI sceNpCondDestroy(Libraries::Kernel::PthreadCondT* cond);
s32 PS4_SYSV_ABI sceNpCondSignal(Libraries::Kernel::PthreadCondT* cond);
s32 PS4_SYSV_ABI sceNpCondTimedwait(Libraries::Kernel::PthreadCondT* cond,
Libraries::Kernel::PthreadMutexT* mutex, u32 usec);
s32 PS4_SYSV_ABI sceNpCreateThread(Libraries::Kernel::PthreadT* thread, NpThreadEntry start_routine,
void* arg, s32 priority, u64 stack_size, u64 affinity_mask,
const char* name);
u32 PS4_SYSV_ABI sceNpJoinThread(Libraries::Kernel::PthreadT thread, void** ret);
s32 PS4_SYSV_ABI sceNpGetSystemClockUsec(s64* usec);
s32 PS4_SYSV_ABI sceNpGetPlatformType(const OrbisNpId* np_id);
s32 PS4_SYSV_ABI sceNpIntIsValidOnlineId(const OrbisNpOnlineId* id);
void PS4_SYSV_ABI sceNpGetSdkVersion(char* version_buf);
s32 PS4_SYSV_ABI sceNpCalloutInitCtx(OrbisNpCalloutContext* ctx, const char* name, u64 stack_size,
s32 priority, u64 affinity_mask);
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
u64 delay_usec, u64 handler, u64 arg);
s32 PS4_SYSV_ABI sceNpCalloutStartOnCtx64(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
s64 delay_usec, u64 handler, u64 arg);
s32 PS4_SYSV_ABI sceNpCalloutStopOnCtx(OrbisNpCalloutContext* ctx, OrbisNpCalloutEntry* callout,
u32* removed);
void PS4_SYSV_ABI sceNpCalloutTermCtx(OrbisNpCalloutContext* ctx);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpCommon
+784
View File
@@ -0,0 +1,784 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
// 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_INVALID_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;
constexpr int ORBIS_NP_INT_ERROR_INVALID_ARGUMENT = 0x8055A283;
constexpr int ORBIS_NP_SCORE_INVALID_ARGUMENT = 0x80550704;
constexpr int ORBIS_NP_WEB_API_INVALID_ARGUMENT = 0x80552902;
// 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_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;
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_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055340a;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c;
constexpr int ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND = 0x8055340d;
constexpr int ORBIS_NP_WEBAPI2_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x8055340e;
constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_BUSY = 0x8055340f;
constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_BUSY = 0x80553410;
constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_BUSY = 0x80553411;
constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_HTTP_STATUS_CODE = 0x80553412;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_HTTP_HEADER = 0x80553413;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_FUNCTION_CALL = 0x80553414;
constexpr int ORBIS_NP_WEBAPI2_ERROR_MULTIPART_PART_NOT_FOUND = 0x80553415;
constexpr int ORBIS_NP_WEBAPI2_ERROR_PARAMETER_TOO_LONG = 0x80553416;
constexpr int ORBIS_NP_WEBAPI2_ERROR_HANDLE_BUSY = 0x80553417;
constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_MAX = 0x80553418;
constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_MAX = 0x80553419;
constexpr int ORBIS_NP_WEBAPI2_ERROR_AFTER_SEND = 0x8055341a;
constexpr int ORBIS_NP_WEBAPI2_ERROR_TIMEOUT = 0x8055341b;
constexpr int ORBIS_NP_WEBAPI2_ERROR_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;
// NP Callout / LW Mutex (0x80558001 - 0x8055800F)
constexpr int ORBIS_NP_CALLOUT_ERROR_NOT_INITIALIZED = 0x80558001;
constexpr int ORBIS_NP_CALLOUT_ERROR_ALREADY_INITIALIZED = 0x80558002;
constexpr int ORBIS_NP_CALLOUT_ERROR_DUPLICATE_ENTRY = 0x80558006;
constexpr int ORBIS_NP_LW_COND_ERROR_TIMEDOUT = 0x8055800B;
constexpr int ORBIS_NP_LW_MUTEX_ERROR_BUSY = 0x8055800F;
// NP Signaling internal errors (0x80559601 - 0x8055960F)
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_INVALID_ARGUMENT = 0x80559601;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_DATA_TOO_BIG = 0x80559602;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_INVALID_ACTION = 0x80559603;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_CONN_NOT_ACTIVATED = 0x80559604;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_OUT_OF_MEMORY = 0x80559605;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_PARSER_FAILED = 0x80559606;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_INVALID_NAMESPACE = 0x80559607;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_ALLOCATOR = 0x80559608;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_IPC_CONTEXT_NOT_FOUND = 0x80559609;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_ALREADY_INITIALIZED = 0x8055960A;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_NOT_INITIALIZED = 0x8055960B;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_XMPP_IPC_INVALID_MSG_TYPE = 0x8055960C;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_XMPP_IPC_INVALID_ID = 0x8055960D;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_INVALID_DATA_TYPE = 0x8055960E;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_ERROR_CALLBACK_QUEUE_OVERFLOW = 0x8055960F;
// NP Signaling internal socket / core errors
constexpr int ORBIS_NP_SIGNALING_INTERNAL_CORE_ERROR_80550E81 = 0x80550E81;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_CORE_ERROR_80550E82 = 0x80550E82;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_CORE_ERROR_80550E83 = 0x80550E83;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SERVICE_ERROR_TRANSIENT_SOCKET_ALLOC = 0x80559611;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SERVICE_ERROR_POLL_PENDING = 0x80559D1B;
// NP Signaling internal socket errors
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SOCKET_ERROR_BASE = 0x80410100;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SOCKET_ERROR_SHORT_IO = 0x80410104;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SOCKET_ERROR_ECHO_RECV_REBUILD = 0x80410123;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SOCKET_ERROR_80410124 = 0x80410124;
// NP Signaling internal STUN errors
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_ALREADY_INITIALIZED = 0x80412401;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_NOT_INITIALIZED = 0x80412402;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_TIMEOUT = 0x80412403;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_BAD_RESPONSE_HEADER = 0x80412404;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_BAD_ATTRIBUTE_LENGTH = 0x80412405;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_PARSE_FAILED = 0x80412406;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_UNSUPPORTED_ADDRESS_FAMILY = 0x80412407;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_NO_SLOT = 0x80412409;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_BAD_QUEUE_INDEX = 0x8041240A;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_MESSAGE_INTEGRITY = 0x8041240B;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_BAD_XOR_MAPPED_ADDRESS = 0x8041240C;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_ALLOC_FAILED = 0x8041240D;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_CANCELLED = 0x8041240E;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_NO_RESULT = 0x8041240F;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_QUEUE_SEND_FAILED = 0x80412410;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_BAD_SERVICE_FLAGS = 0x80412411;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_STUN_ERROR_SERVICE_UNAVAILABLE = 0x80412412;
// NP Signaling internal UPNP errors
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_INITIALIZATION_FAILED = 0x80414101;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_NET_UNAVAILABLE = 0x80414102;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_INVALID_ARGUMENT = 0x80414105;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_DISCOVERY_PENDING = 0x80414108;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_HTTP_SOCKET_FAILED = 0x8041410A;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SERVICE_NOT_FOUND = 0x8041410B;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_INVALID_ACTION = 0x80414171;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_INVALID_ARGS = 0x80414172;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_INVALID_VAR = 0x80414173;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_ACTION_FAILED = 0x80414174;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_WILDCARD_NOT_PERMITTED = 0x80414175;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_EXTERNAL_PORT_WILDCARD_ONLY =
0x80414176;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_CONFLICT_MAPPING_ENTRY = 0x80414177;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_SAME_PORT_VALUES_REQUIRED =
0x80414178;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_ONLY_PERMANENT_LEASE_SUPPORTED =
0x80414179;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_REMOTE_HOST_WILDCARD_ONLY =
0x8041417A;
constexpr int
ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_EXTERNAL_PORT_WILDCARD_NOT_PERMITTED =
0x8041417B;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_NO_PORT_MAPS_AVAILABLE = 0x8041417C;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SOAP_FAULT_GENERIC = 0x8041417D;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_8041417E = 0x8041417E;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_80414181 = 0x80414181;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_80414184 = 0x80414184;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_FORMAT_OR_BUFFER = 0x80414185;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SSDP_ADDR = 0x80414186;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SSDP_TIMEOUT = 0x80414187;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_80414188 = 0x80414188;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_HTTP_URL_INVALID = 0x80414189;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_SSDP_IGNORED = 0x8041418A;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_8041418B = 0x8041418B;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_CONTENT_LENGTH_MISMATCH = 0x8041418D;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_UNSUPPORTED_ACTION = 0x8041418F;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_80414190 = 0x80414190;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_XML_ARGUMENT = 0x804141A0;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_XML_INPUT = 0x804141A1;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_UPNP_ERROR_XML_SPACE = 0x804141A2;
// NP Signaling internal misc errors
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SHA1_ERROR_INVALID_ARGUMENT = 0x80480300;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_SHA1_ERROR_INACTIVE_CONTEXT = 0x80480302;
constexpr int ORBIS_NP_SIGNALING_INTERNAL_HMAC_ERROR_INVALID_ARGUMENT = 0x80481000;
File diff suppressed because it is too large. Load diff
+325
View File
@@ -0,0 +1,325 @@
// 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 <atomic>
#include <chrono>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#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/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();
// Connect/disconnect a single user in response to a user service login/logout event.
void OnUserLoggedIn(s32 user_id);
void OnUserLoggedOut(s32 user_id);
// 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;
// Set the Appear-Offline preference for all signed-in users (and future logins). While
// enabled, shadNet handles the user as offline for everyone else. Call from the UI/config.
void SetAppearOffline(bool enable);
bool IsAppearOffline() const {
return m_appear_offline.load();
}
/// Full NP ID for this user, built once from shadnet_npid after login.
OrbisNpId GetNpId(s32 user_id) const;
/// The Online ID embedded in the NP ID (npid.handle).
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;
// WebAPI bearer token
std::string GetBearerToken(s32 user_id) const;
// Sends a session invitation via the WebAPI (POST /v1/sessions/<id>/invitations). Each 'to'
// entry is an online ID (npid) or a decimal account ID
bool SendSessionInvitation(s32 user_id, const std::string& session_id,
const std::vector<std::string>& to, const std::string& message);
// Raises ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION (0x10000002) so titles that watch the
// system-service event learn about the invite.
void PostSessionInvitationEvent(const std::string& session_id, const std::string& invitation_id,
const std::string& accepter_online_id);
// A session invitation surfaced from a shadNet push, stashed until the user acts on it.
struct PendingInvitation {
std::string session_id;
std::string invitation_id;
std::string from_npid; // sender (for display)
std::string to_npid; // local recipient / accepter
int64_t valid_until = 0; // ms since epoch; 0 = never expires
};
// Pending invitations stashed for a local user (newest last), populated on arrival.
std::vector<PendingInvitation> GetPendingInvitations(s32 user_id) const;
// Consumes an invitation server-side (PUT usedFlag=true) and drops it from the stash. Returns
// true if the invite was found and the consume succeeded.
bool AcceptSessionInvitation(s32 user_id, const std::string& invitation_id);
// Dismisses an invitation locally by dropping it from the stash. No server call (a declined
// invite is left unconsumed and simply ages out server-side).
void DeclineSessionInvitation(s32 user_id, const std::string& invitation_id);
// 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<std::string> GetFriendNpid(s32 user_id, u32 index) const;
// ---- Friend list / requests / blocked (shadNet) ----
struct FriendInfo {
std::string npid;
bool online = false;
};
struct FriendListSnapshot {
std::vector<FriendInfo> friends;
std::vector<std::string> requests_received;
std::vector<std::string> requests_sent;
std::vector<std::string> blocked;
};
// Local users that currently have an authenticated shadNet session.
std::vector<s32> GetConnectedUsers() const;
// user's friend state for UI display.
FriendListSnapshot GetFriendList(s32 user_id) const;
// Friend/block actions
s32 SendFriendRequest(s32 user_id, const std::string& npid);
s32 RemoveFriend(s32 user_id, const std::string& npid);
s32 BlockUser(s32 user_id, const std::string& npid);
s32 UnblockUser(s32 user_id, const std::string& npid);
// 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<NpScore::ScoreRequestCtx> req);
s32 RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 pcId, s64 score,
const u8* data, size_t size, std::shared_ptr<NpScore::ScoreRequestCtx> 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<NpScore::ScoreRequestCtx> req);
s32 GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardId, u64 accountId, s32 pcId,
void* dataOut, u64 recvSize, u64* totalSizeOut,
std::shared_ptr<NpScore::ScoreRequestCtx> req);
s32 GetBoardInfo(s32 user_id, s32 service_label, u32 boardId,
NpScore::OrbisNpScoreBoardInfo* boardInfo,
std::shared_ptr<NpScore::ScoreRequestCtx> req);
// Submit a GetRankingByNpId request to the shadNet server.
s32 GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId,
const std::vector<std::string>& npIds, const std::vector<s32>& pcIds,
NpScore::OrbisNpScorePlayerRankData* rankArray,
NpScore::OrbisNpScoreComment* commentArray,
NpScore::OrbisNpScoreGameInfo* infoArray,
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
std::shared_ptr<NpScore::ScoreRequestCtx> 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<NpScore::ScoreRequestCtx> 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<NpScore::ScoreRequestCtx> req);
s32 GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId,
const std::vector<u64>& accountIds, const std::vector<s32>& pcIds,
NpScore::OrbisNpScorePlayerRankDataA* rankArray,
NpScore::OrbisNpScoreComment* commentArray,
NpScore::OrbisNpScoreGameInfo* infoArray,
Libraries::Rtc::OrbisRtcTick* lastSortDate, u32* totalRecord,
std::shared_ptr<NpScore::ScoreRequestCtx> 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<NpScore::ScoreRequestCtx> 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<NpScore::ScoreRequestCtx> req);
// State callbacks
using StateCallback = std::function<void(Libraries::UserService::OrbisUserServiceUserId user_id,
NpManager::OrbisNpState state)>;
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);
// Connect a single logged-in user by id (looks up credentials, parses server).
bool ConnectUserById(s32 user_id);
// Parse the configured shadNet server "host:port" (default port 31313).
std::pair<std::string, u16> ParseServerAddress() const;
// Start the health-monitor worker thread if not already running (idempotent).
void StartWorker();
// Disconnect and remove one user's client.
void DisconnectUser(s32 user_id);
void WorkerThread();
// Transparent reconnect after a network drop
void MarkForReconnect(s32 user_id);
void TryReconnect();
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);
void OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEvent& n);
void OnLoginResult(s32 user_id, const ShadNet::LoginResult& res);
// General async reply dispatch. Routes a reply to the owning subsystem by
// command. Called from the per-user ShadNetClient on the reader thread.
void OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
const std::vector<u8>& body);
void OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
const std::vector<u8>& body);
// 12-byte NP Communication ID
std::string GetNpCommId(s32 service_label) const;
// Appear-Offline preference, applied to every client at login and on change.
std::atomic<bool> m_appear_offline{false};
// Per-user client map
mutable std::mutex m_mutex_clients;
std::map<s32, std::shared_ptr<ShadNet::ShadNetClient>> m_clients;
// Per-user NP ID built once from shadnet_npid after login.
std::map<s32, OrbisNpId> m_np_ids;
// Score requests awaiting a reply, keyed by the submit packet id.
struct PendingScoreRequest {
std::shared_ptr<NpScore::ScoreRequestCtx> req;
ShadNet::CommandType cmd;
std::vector<std::string> 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<u64, PendingScoreRequest> m_pending_score;
// Worker thread
std::atomic<bool> m_initialized{false};
std::atomic<bool> m_worker_running{false};
std::thread m_worker_thread;
// Users dropped by a network error and awaiting transparent reconnect
struct ReconnectState {
std::chrono::steady_clock::time_point next_attempt{};
std::chrono::milliseconds backoff{0};
};
std::unordered_map<s32, ReconnectState> m_reconnect;
mutable std::mutex m_mutex_pending_invites;
std::unordered_map<s32, std::vector<PendingInvitation>> m_pending_invites;
// State callbacks
struct CbEntry {
s32 handle;
StateCallback cb;
void* userdata;
};
mutable std::mutex m_mutex_cbs;
std::vector<CbEntry> m_state_cbs;
s32 m_next_handle{1};
// Friend state per user, seeded from LoginReply and updated by notifications/actions.
mutable std::mutex m_mutex_friend_state;
std::map<s32, FriendListSnapshot> m_friend_state;
};
} // namespace Libraries::Np
File diff suppressed because it is too large. Load diff
+125
View File
@@ -0,0 +1,125 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include "common/types.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpManager {
constexpr s32 ORBIS_NP_MANAGER_REQUEST_LIMIT = 0x20;
constexpr s32 ORBIS_NP_MANAGER_REQUEST_ID_OFFSET = 0x20000000;
enum class OrbisNpState : u32 {
Unknown = 0,
SignedOut = 1,
SignedIn = 2,
};
enum class OrbisNpReachabilityState {
Unavailable = 0,
Available = 1,
Reachable = 2,
};
using OrbisNpStateCallback =
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state,
OrbisNpId* npId, void* userdata);
using OrbisNpStateCallbackA = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
using OrbisNpReachabilityStateCallback =
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId,
OrbisNpReachabilityState state, void* userdata);
enum class OrbisNpGamePresenseStatus {
Offline = 0,
Online = 1,
};
struct OrbisNpCountryCode {
char country_code[2];
char end;
char pad;
};
struct OrbisNpAgeRestriction {
OrbisNpCountryCode country_code;
s8 age;
u8 padding[3];
};
struct OrbisNpContentRestriction {
u64 size;
s8 default_age_restriction;
u8 padding[3];
s32 age_restriction_count;
const OrbisNpAgeRestriction* age_restriction;
};
struct OrbisNpDate {
u16 year;
u8 month;
u8 day;
};
struct OrbisNpLanguageCode {
char code[6];
char padding[10];
};
struct OrbisNpParentalControlInfo {
bool content_restriction;
bool chat_restriction;
bool user_generated_content_restriction;
};
struct OrbisNpCheckPlusParameter {
u64 size;
Libraries::UserService::OrbisUserServiceUserId user_id;
u8 padding[4];
u64 features;
u8 reserved[32];
};
struct OrbisNpCheckPlusResult {
bool authorized;
u8 reserved[32];
};
struct OrbisNpCreateAsyncRequestParameter {
u64 size;
u64 cpu_affinity_mask;
s32 thread_priority;
u8 padding[4];
};
void RegisterNpCallback(std::string key, std::function<void()> cb);
void DeregisterNpCallback(std::string key);
void NotifyNpStateFromUserServiceEvent(Libraries::UserService::OrbisUserServiceEventType event_type,
Libraries::UserService::OrbisUserServiceUserId user_id);
s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpId* np_id);
s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpOnlineId* online_id);
s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id,
Libraries::UserService::OrbisUserServiceUserId user_id);
s32 PS4_SYSV_ABI sceNpGetAccountLanguageA(s32 req_id,
Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpLanguageCode* language);
s32 PS4_SYSV_ABI
sceNpGetParentalControlInfoA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id,
s8* age, OrbisNpParentalControlInfo* info);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpManager
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types2.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpMatching2 {
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param);
int PS4_SYSV_ABI sceNpMatching2Terminate();
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
OrbisNpMatching2ContextId* ctxId);
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
OrbisNpMatching2ContextId* ctxId);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpMatching2
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,442 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_types.h"
namespace shadnet {
class CreateJoinRoomResponse;
class GetWorldInfoListReply;
class GetRoomDataExternalListReply;
class GetRoomMemberDataExternalListReply;
class GetUserInfoListReply;
class LeaveRoomReply;
class SearchRoomReply;
} // namespace shadnet
namespace Libraries::Np::NpMatching2 {
struct PeerInfo {
u32 addr = 0;
u16 port = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
s32 conn_id = 0;
s32 status = 0;
Libraries::Np::OrbisNpOnlineId online_id{};
bool handshake_started = false;
bool sent_check = false;
bool sent_established = false;
u64 nonce = 0;
u32 ping_us = 0;
std::chrono::steady_clock::time_point last_send{};
std::chrono::steady_clock::time_point last_check_send{};
};
struct MemberBinCache {
OrbisNpMatching2AttributeId id = 0;
u64 update_date = 0;
std::vector<u8> data;
};
struct MemberCache {
Libraries::Np::OrbisNpId np_id{};
Libraries::Np::OrbisNpAccountId account_id = 0;
Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None;
u64 join_date = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
OrbisNpMatching2TeamId team_id = 0;
OrbisNpMatching2RoomGroupId group_id = 0;
OrbisNpMatching2NatType nat_type = 0;
OrbisNpMatching2Flags flag_attr = 0;
u32 addr = 0;
u16 port = 0;
std::map<OrbisNpMatching2AttributeId, MemberBinCache> bins;
};
struct RoomCache {
u32 num_slots = 0;
u64 mask_password = 0;
OrbisNpMatching2SignalingType signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH;
OrbisNpMatching2RoomMemberId signaling_main_member = 0;
OrbisNpMatching2ServerId server_id = 0;
OrbisNpMatching2WorldId world_id = 0;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2RoomId room_id = 0;
u16 max_slot = 0;
u16 public_slots = 0;
u16 private_slots = 0;
u16 open_public_slots = 0;
u16 open_private_slots = 0;
u64 passwd_slot_mask = 0;
u64 joined_slot_mask = 0;
OrbisNpMatching2Flags flags = 0;
std::vector<OrbisNpMatching2RoomBinAttrInternal> bin_attrs_internal;
std::vector<std::vector<u8>> bin_buffers;
std::map<OrbisNpMatching2RoomGroupId, OrbisNpMatching2RoomGroup> groups;
std::map<OrbisNpMatching2RoomMemberId, MemberCache> members;
bool owner = false;
};
struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomDataInternal> room_data;
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponse> create_join_response;
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponseA> create_join_response_a;
std::unique_ptr<OrbisNpMatching2LeaveRoomResponse> leave_room_response;
std::unique_ptr<OrbisNpMatching2SearchRoomResponse> search_room_response;
std::unique_ptr<OrbisNpMatching2SearchRoomResponseA> search_room_response_a;
std::unique_ptr<OrbisNpMatching2GetRoomDataExternalListResponse>
room_data_external_list_response;
std::unique_ptr<OrbisNpMatching2GetRoomDataExternalListResponseA>
room_data_external_list_response_a;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponse>
room_member_data_external_list_response;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponseA>
room_member_data_external_list_response_a;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponse> user_info_list_response;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponseA> user_info_list_response_a;
std::unique_ptr<OrbisNpMatching2GetWorldInfoListResponse> world_info_response;
std::unique_ptr<OrbisNpMatching2SignalingGetPingInfoResponse> ping_info_response;
std::vector<OrbisNpMatching2World> world_list;
std::vector<OrbisNpMatching2RoomMemberDataInternal> member_data;
std::vector<OrbisNpMatching2RoomMemberDataInternalA> member_data_a;
std::vector<OrbisNpMatching2RoomMemberDataExternal> member_data_external;
std::vector<OrbisNpMatching2RoomMemberDataExternalA> member_data_external_a;
std::vector<OrbisNpMatching2RoomGroup> room_groups;
std::vector<OrbisNpMatching2RoomGroup*> room_group_ptrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal> room_bin_attrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal*> room_bin_attr_ptrs;
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal> member_bin_attrs;
std::vector<OrbisNpMatching2RoomDataExternal> room_data_external;
std::vector<OrbisNpMatching2RoomDataExternalA> room_data_external_a;
std::vector<OrbisNpMatching2UserInfo> user_info;
std::vector<OrbisNpMatching2UserInfoA> user_info_a;
std::vector<OrbisNpMatching2BinAttr> user_bin_attrs;
std::vector<std::vector<u8>> bin_buffers;
std::vector<OrbisNpMatching2IntAttr> ext_int_attrs;
std::vector<OrbisNpMatching2BinAttr> ext_bin_attrs;
std::vector<OrbisNpMatching2RoomGroupInfo> ext_room_groups;
std::vector<Libraries::Np::OrbisNpId> ext_owner_npids;
void* request_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMessageInfo> room_message_info;
std::unique_ptr<OrbisNpMatching2RoomMessageInfoA> room_message_info_a;
std::unique_ptr<OrbisNpMatching2RoomMessageDestination> room_message_dst;
std::unique_ptr<Libraries::Np::OrbisNpId> room_message_src_npid;
std::unique_ptr<Libraries::Np::OrbisNpPeerAddressA> room_message_src_addr;
std::unique_ptr<Libraries::Np::OrbisNpOnlineId> room_message_src_online_id;
std::vector<OrbisNpMatching2RoomMemberId> room_message_multicast_members;
std::vector<u8> room_message_data;
void* room_message_callback_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdateA> room_member_update_a;
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
std::unique_ptr<OrbisNpMatching2RoomDataInternalUpdate> room_data_internal_update;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_chg_flag_attr;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_prev_flag_attr;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_chg_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_prev_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternal> event_member;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalA> event_member_a;
void* room_event_data = nullptr;
CallbackPayload() = default;
CallbackPayload(const CallbackPayload&) = delete;
CallbackPayload& operator=(const CallbackPayload&) = delete;
CallbackPayload(CallbackPayload&&) = delete;
CallbackPayload& operator=(CallbackPayload&&) = delete;
void Reset() {
room_data.reset();
create_join_response.reset();
create_join_response_a.reset();
leave_room_response.reset();
search_room_response.reset();
search_room_response_a.reset();
room_data_external_list_response.reset();
room_data_external_list_response_a.reset();
room_member_data_external_list_response.reset();
room_member_data_external_list_response_a.reset();
user_info_list_response.reset();
user_info_list_response_a.reset();
world_info_response.reset();
ping_info_response.reset();
world_list.clear();
member_data.clear();
member_data_a.clear();
member_data_external.clear();
member_data_external_a.clear();
room_groups.clear();
room_group_ptrs.clear();
room_bin_attrs.clear();
room_bin_attr_ptrs.clear();
member_bin_attrs.clear();
room_data_external.clear();
room_data_external_a.clear();
user_info.clear();
user_info_a.clear();
user_bin_attrs.clear();
bin_buffers.clear();
ext_int_attrs.clear();
ext_bin_attrs.clear();
ext_room_groups.clear();
ext_owner_npids.clear();
request_data = nullptr;
room_message_info.reset();
room_message_info_a.reset();
room_message_dst.reset();
room_message_src_npid.reset();
room_message_src_addr.reset();
room_message_src_online_id.reset();
room_message_multicast_members.clear();
room_message_data.clear();
room_message_callback_data = nullptr;
room_member_update.reset();
room_member_update_a.reset();
room_update.reset();
room_data_internal_update.reset();
event_chg_flag_attr.reset();
event_prev_flag_attr.reset();
event_chg_passwd_slot_mask.reset();
event_prev_passwd_slot_mask.reset();
event_member.reset();
event_member_a.reset();
room_event_data = nullptr;
}
};
struct ContextObject {
OrbisNpMatching2ContextId ctx_id = 0;
bool started = false;
bool stop_pending = false;
bool destroy_pending = false;
bool a_variant = false;
OrbisNpMatching2ServerId server_id = 1;
OrbisNpServiceLabel service_label = 0;
Libraries::Np::OrbisNpId owner_np_id{};
Libraries::Np::OrbisNpOnlineId online_id{};
u64 handler_registration_generation = 1;
OrbisNpMatching2WorldId world_id = 0;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2RoomId room_id = 0;
OrbisNpMatching2RoomMemberId my_member_id = 0;
bool is_room_owner = false;
u32 max_slot = 5;
OrbisNpMatching2Flags flag_attr = 0;
std::map<OrbisNpMatching2RoomMemberId, PeerInfo> peers;
std::map<OrbisNpMatching2RoomId, RoomCache> room_cache;
CallbackPayload request_payload;
CallbackPayload* request_payload_override = nullptr;
CallbackPayload room_event_payload;
OrbisNpMatching2ContextCallback context_callback = nullptr;
void* context_callback_arg = nullptr;
OrbisNpMatching2RequestCallback default_request_callback = nullptr;
void* default_request_callback_arg = nullptr;
OrbisNpMatching2RequestCallback per_request_callback = nullptr;
void* per_request_callback_arg = nullptr;
OrbisNpMatching2RoomEventCallback room_event_callback = nullptr;
void* room_event_callback_arg = nullptr;
OrbisNpMatching2RoomMessageCallback room_message_callback = nullptr;
void* room_message_callback_arg = nullptr;
OrbisNpMatching2LobbyEventCallback lobby_event_callback = nullptr;
void* lobby_event_callback_arg = nullptr;
OrbisNpMatching2LobbyMessageCallback lobby_message_callback = nullptr;
void* lobby_message_callback_arg = nullptr;
OrbisNpMatching2SignalingCallback signaling_callback = nullptr;
void* signaling_callback_arg = nullptr;
void Reset() {
ctx_id = 0;
started = false;
stop_pending = false;
destroy_pending = false;
a_variant = false;
server_id = 1;
service_label = 0;
owner_np_id = {};
online_id = {};
handler_registration_generation = 1;
world_id = 0;
lobby_id = 0;
room_id = 0;
my_member_id = 0;
is_room_owner = false;
max_slot = 5;
flag_attr = 0;
peers.clear();
room_cache.clear();
request_payload.Reset();
request_payload_override = nullptr;
room_event_payload.Reset();
context_callback = nullptr;
context_callback_arg = nullptr;
default_request_callback = nullptr;
default_request_callback_arg = nullptr;
per_request_callback = nullptr;
per_request_callback_arg = nullptr;
room_event_callback = nullptr;
room_event_callback_arg = nullptr;
room_message_callback = nullptr;
room_message_callback_arg = nullptr;
lobby_event_callback = nullptr;
lobby_event_callback_arg = nullptr;
lobby_message_callback = nullptr;
lobby_message_callback_arg = nullptr;
signaling_callback = nullptr;
signaling_callback_arg = nullptr;
}
};
struct RequestCallbackInfo {
OrbisNpMatching2RequestCallback callback = nullptr;
void* arg = nullptr;
};
class ContextManager {
public:
static constexpr u32 kMaxContexts = 10;
static ContextManager& Instance();
s32 CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label,
OrbisNpMatching2ContextId* out_ctx_id, bool a_variant = false);
bool Check(OrbisNpMatching2ContextId ctx_id);
ContextObject* Get(OrbisNpMatching2ContextId ctx_id);
bool Destroy(OrbisNpMatching2ContextId ctx_id);
void CompleteStop(OrbisNpMatching2ContextId ctx_id);
s32 Start(OrbisNpMatching2ContextId ctx_id);
s32 Stop(OrbisNpMatching2ContextId ctx_id);
void ApplyContextCallback(OrbisNpMatching2ContextCallback callback, void* arg);
void Reset();
private:
ContextManager() = default;
ContextObject* GetLocked(OrbisNpMatching2ContextId ctx_id);
std::mutex m_mutex;
std::array<ContextObject, kMaxContexts + 1> m_contexts{};
std::array<bool, kMaxContexts + 1> m_used{};
OrbisNpMatching2ContextId m_next_id = 1;
OrbisNpMatching2ContextCallback m_pending_context_callback = nullptr;
void* m_pending_context_callback_arg = nullptr;
};
struct PendingEvent {
enum Type {
CONTEXT_CB,
REQUEST_CB,
SIGNALING_CB,
ROOM_EVENT_CB,
LOBBY_EVENT_CB,
LOBBY_MESSAGE_CB,
ROOM_MESSAGE_CB,
};
Type type = CONTEXT_CB;
OrbisNpMatching2ContextId ctx_id = 0;
std::chrono::steady_clock::time_point fire_at;
OrbisNpMatching2Event ctx_event{};
OrbisNpMatching2EventCause ctx_event_cause{};
s32 error_code = 0;
OrbisNpMatching2RequestId req_id = 0;
OrbisNpMatching2Event req_event{};
OrbisNpMatching2RequestCallback request_cb = nullptr;
void* request_cb_arg = nullptr;
void* request_data = nullptr;
std::shared_ptr<CallbackPayload> request_payload_owner;
OrbisNpMatching2RoomId room_id = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
OrbisNpMatching2Event sig_event{};
u32 conn_id = 0;
OrbisNpMatching2Event room_event{};
void* room_event_data = nullptr;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2Event lobby_event{};
void* lobby_event_data = nullptr;
OrbisNpMatching2RoomMemberId src_member_id = 0;
OrbisNpMatching2Event msg_event{};
void* message_data = nullptr;
};
struct NpMatching2State {
std::atomic<bool> initialized = false;
OrbisNpMatching2RequestId next_request_id = 1;
std::mutex mutex;
Kernel::PthreadT dispatch_thread = nullptr;
std::mutex queue_mutex;
std::condition_variable queue_cv;
std::vector<PendingEvent> pending_events;
std::atomic<bool> dispatch_running{false};
};
extern NpMatching2State g_state;
OrbisNpMatching2RequestId AllocRequestId();
bool IsInitialized();
void SetInitialized(bool initialized);
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt);
RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx);
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp);
void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp);
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayload(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayloadA(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType,
const std::vector<OrbisNpMatching2RoomMemberId>& dstMembers,
const MemberCache* srcMember, const std::vector<u8>& msg);
void InitEventDispatcher();
void TermEventDispatcher();
void ScheduleEvent(PendingEvent ev);
PS4_SYSV_ABI void* EventDispatcherThreadMain(void* arg);
} // namespace Libraries::Np::NpMatching2
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <vector>
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
namespace ShadNet {
class ShadNetClient;
enum class CommandType : u16;
enum class ErrorType : u8;
} // namespace ShadNet
namespace Libraries::Np::NpMatching2 {
enum class MmCommand : u16 {
ContextStart = 100,
CreateRoom = 101,
JoinRoom = 102,
LeaveRoom = 103,
SearchRoom = 104,
RequestSignalingInfos = 105,
ContextStop = 106,
SetUserInfo = 107,
SetRoomDataInternal = 108,
SetRoomDataExternal = 109,
KickoutRoomMember = 110,
GetWorldInfoList = 111,
GetRoomDataExternalList = 112,
GetUserInfoList = 113,
GetRoomMemberDataExternalList = 114,
SendRoomMessage = 115,
};
void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
std::string_view server_host, u16 tcp_port);
void ClearMmShadNetClient();
bool IsMmClientRunning();
void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error,
const std::vector<u8>& body);
void MmContextStart(OrbisNpMatching2ContextId ctx_id);
s32 MmContextStop(OrbisNpMatching2ContextId ctx_id);
s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector<u8>& payload,
bool a_variant = false);
s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2CreateJoinRoomRequest& request);
s32 MmCreateJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2CreateJoinRoomRequestA& request);
s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequest& request);
s32 MmJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequestA& request);
s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2LeaveRoomRequest& request);
s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetWorldInfoListRequest& request);
s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SearchRoomRequest& request, bool a_variant = false);
s32 MmGetRoomDataExternalList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomDataExternalListRequest& request,
bool a_variant = false);
s32 MmGetRoomMemberDataExternalList(
OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetRoomMemberDataExternalListRequest& request, bool a_variant = false);
s32 MmGetUserInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2GetUserInfoListRequest& request,
bool a_variant = false);
s32 MmSetUserInfo(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetUserInfoRequest& request);
s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SendRoomMessageRequest& request);
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataInternalRequest& request);
s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataExternalRequest& request);
s32 MmKickoutRoomMember(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2KickoutRoomMemberRequest& request);
u32 GetMmServerAddr();
u16 GetMmServerUdpPort();
bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16* out_port);
} // namespace Libraries::Np::NpMatching2
@@ -0,0 +1,617 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include "common/logging/log.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/sockets.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
namespace Libraries::Np::NpMatching2 {
namespace {
constexpr s32 kMatching2ConnInactive = 0;
constexpr s32 kMatching2ConnPending = 1;
constexpr s32 kMatching2ConnActive = 2;
constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350);
constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10);
constexpr auto kMatching2StunPingInterval = std::chrono::seconds(5);
std::atomic<bool> g_matching2_stop{false};
std::mutex g_matching2_thread_mutex;
std::thread g_matching2_thread;
enum class Matching2HandshakeKind : u8 {
Offer = 1,
Accept = 2,
Check = 3,
CheckAck = 4,
};
#pragma pack(push, 1)
struct Matching2HandshakePacket {
u8 magic[4] = {'S', 'H', 'A', 'D'};
u8 type = 0x21;
u8 kind = 0;
u64 room_id = 0;
u16 from_member_id = 0;
u16 to_member_id = 0;
u8 online_id_from[16]{};
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reserved = 0;
u64 nonce = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2HandshakePacket) == 0x32);
#pragma pack(push, 1)
struct Matching2StunPing {
u8 cmd = 0x01;
u8 online_id[ORBIS_NP_ONLINEID_MAX_LENGTH]{};
u32 local_ip = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2StunPing) == 21);
bool HasMatching2Magic(const Matching2HandshakePacket& pkt) {
return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' &&
pkt.magic[3] == 'D' && pkt.type == 0x21;
}
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
return std::string(buf);
}
bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self,
OrbisNpMatching2RoomMemberId peer) {
if (peer == 0 || peer == self) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR &&
room.signaling_main_member != 0) {
return self == room.signaling_main_member || peer == room.signaling_main_member;
}
return true;
}
bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
if (peer.addr != 0 && peer.port != 0) {
return true;
}
if (member.addr != 0 && member.port != 0) {
peer.addr = member.addr;
peer.port = member.port;
if (peer.online_id.data[0] == 0) {
peer.online_id = member.np_id.handle;
}
return true;
}
const std::string online_id(member.np_id.handle.data);
u32 resolved_addr = 0;
u16 resolved_port = 0;
if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) {
peer.addr = resolved_addr;
peer.port = resolved_port;
}
if (peer.online_id.data[0] == 0) {
peer.online_id = member.np_id.handle;
}
return peer.addr != 0 && peer.port != 0;
}
void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) {
if (member_id == 0 || member_id == ctx.my_member_id) {
return;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (addr != 0) {
peer.addr = addr;
}
if (port != 0) {
peer.port = port;
}
const bool first_active = peer.status != kMatching2ConnActive;
peer.status = kMatching2ConnActive;
peer.handshake_started = true;
if (first_active && !peer.sent_established) {
peer.sent_established = true;
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK);
LOG_INFO(Lib_NpMatching2,
"Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}",
ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port));
}
}
bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind,
u64 nonce) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return false;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return false;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (!ResolvePeerEndpoint(member_it->second, peer)) {
LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}",
room_id, member_id);
return false;
}
Matching2HandshakePacket pkt{};
pkt.kind = static_cast<u8>(kind);
pkt.room_id = room_id;
pkt.from_member_id = ctx.my_member_id;
pkt.to_member_id = member_id;
std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
pkt.mapped_addr = Net::GetP2PAdvertisedAddr();
pkt.mapped_port = Net::GetP2PConfiguredPort() != 0
? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort())
: 0;
pkt.nonce = nonce;
const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port);
const auto now = std::chrono::steady_clock::now();
peer.last_send = now;
if (kind == Matching2HandshakeKind::Check) {
peer.last_check_send = now;
}
LOG_DEBUG(Lib_NpMatching2,
"Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}",
static_cast<u8>(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr,
Libraries::Net::sceNetNtohs(peer.port), rc);
return rc >= 0;
}
ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) {
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) {
continue;
}
if (ctx->room_cache.find(static_cast<OrbisNpMatching2RoomId>(pkt.room_id)) !=
ctx->room_cache.end()) {
return ctx;
}
}
return nullptr;
}
void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
const Matching2HandshakePacket& pkt) {
if (!HasMatching2Magic(pkt)) {
return;
}
ContextObject* ctx = FindContextForMatching2Packet(pkt);
if (!ctx) {
LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}",
pkt.room_id, pkt.to_member_id);
return;
}
const auto room_id = static_cast<OrbisNpMatching2RoomId>(pkt.room_id);
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(pkt.from_member_id);
auto room_it = ctx->room_cache.find(room_id);
if (room_it == ctx->room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) {
return;
}
PeerInfo& peer = ctx->peers[member_id];
peer.member_id = member_id;
// The UDP source endpoint is what this peer's NAT actually exposed to us.
// Prefer it over the self-reported mapping, because a NAT can translate
// the sender's local UDP port.
peer.addr = from_addr != 0 ? from_addr : pkt.mapped_addr;
peer.port = from_port != 0 ? from_port : pkt.mapped_port;
peer.status =
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
peer.handshake_started = true;
SetNpOnlineId(peer.online_id,
std::string_view(reinterpret_cast<const char*>(pkt.online_id_from),
ORBIS_NP_ONLINEID_MAX_LENGTH));
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
switch (kind) {
case Matching2HandshakeKind::Offer:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0);
break;
case Matching2HandshakeKind::Accept:
peer.sent_check = true;
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce);
break;
case Matching2HandshakeKind::Check:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck,
pkt.nonce);
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
break;
case Matching2HandshakeKind::CheckAck:
if (pkt.nonce == 0 || pkt.nonce == peer.nonce) {
if (peer.last_check_send.time_since_epoch().count() != 0) {
const auto rtt = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - peer.last_check_send);
peer.ping_us =
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
std::numeric_limits<u32>::max()));
peer.last_check_send = {};
}
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
}
break;
default:
break;
}
}
void Matching2HandshakeThreadMain() {
auto last_stun_ping = std::chrono::steady_clock::time_point{};
while (!g_matching2_stop.load(std::memory_order_relaxed)) {
Matching2HandshakePacket pkt{};
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port);
if (rc == sizeof(pkt)) {
HandleMatching2HandshakePacket(from_addr, from_port, pkt);
}
const auto now = std::chrono::steady_clock::now();
const bool should_stun_ping = last_stun_ping.time_since_epoch().count() == 0 ||
now - last_stun_ping >= kMatching2StunPingInterval;
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx) {
continue;
}
if (should_stun_ping) {
SendMatching2StunPing(*ctx);
}
if (ctx->room_id == 0) {
continue;
}
auto room_it = ctx->room_cache.find(ctx->room_id);
if (room_it == ctx->room_cache.end()) {
continue;
}
for (auto& [member_id, peer] : ctx->peers) {
if (peer.status != kMatching2ConnPending || !peer.handshake_started) {
continue;
}
if (peer.last_send.time_since_epoch().count() != 0 &&
now - peer.last_send > kMatching2HandshakeTimeout) {
peer.status = kMatching2ConnInactive;
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
continue;
}
if (peer.last_send.time_since_epoch().count() == 0 ||
now - peer.last_send >= kMatching2HandshakeRetry) {
SendMatching2Handshake(*ctx, ctx->room_id, member_id,
peer.sent_check ? Matching2HandshakeKind::Check
: Matching2HandshakeKind::Offer,
peer.sent_check ? peer.nonce : 0);
}
}
}
if (should_stun_ping) {
last_stun_ping = now;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
} // namespace
bool SendMatching2StunPing(const ContextObject& ctx) {
if (!NpSignaling::Stubs::Matching2Enabled()) {
return false;
}
if (!ctx.started || ctx.online_id.data[0] == '\0') {
return false;
}
if (!NpSignaling::Stubs::EnsureTransport()) {
return false;
}
const u32 server_addr = NpSignaling::Stubs::MmServerAddr();
const u16 server_udp = NpSignaling::Stubs::MmServerUdpPort();
if (server_addr == 0 || server_udp == 0) {
return false;
}
Matching2StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = NpSignaling::Stubs::AdvertisedAddr();
const int rc =
NpSignaling::Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
LOG_DEBUG(Lib_NpMatching2,
"Matching2 STUN ping: ctx={} online_id='{}' server={:#x}:{} local_ip={:#x} rc={}",
ctx.ctx_id, OnlineIdToString(ctx.online_id), server_addr,
Libraries::Net::sceNetNtohs(server_udp), ping.local_ip, rc);
return rc >= 0;
}
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code) {
PendingEvent ev{};
ev.type = PendingEvent::SIGNALING_CB;
ev.ctx_id = ctx.ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.member_id = member_id;
ev.sig_event = event;
ev.error_code = error_code;
ScheduleEvent(std::move(ev));
}
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) {
return;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return;
}
Net::EnsureP2PTransport();
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (peer.status == kMatching2ConnActive) {
return;
}
ResolvePeerEndpoint(member_it->second, peer);
peer.status = kMatching2ConnPending;
peer.handshake_started = true;
peer.sent_check = false;
if (peer.nonce == 0) {
peer.nonce = static_cast<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
(static_cast<u64>(ctx.ctx_id) << 48) ^
(static_cast<u64>(member_id) << 16));
}
SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0);
}
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
const auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
StartMatching2PeerHandshake(ctx, room_id, member_id);
}
}
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == 0 || member_id == ctx.my_member_id) {
continue;
}
auto peer_it = ctx.peers.find(member_id);
if (peer_it != ctx.peers.end()) {
peer_it->second.status = kMatching2ConnInactive;
peer_it->second.handshake_started = false;
peer_it->second.sent_check = false;
}
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code);
LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}",
ctx.ctx_id, room_id, member_id, static_cast<u32>(error_code));
}
}
void StartMatching2HandshakeThread() {
std::lock_guard lock(g_matching2_thread_mutex);
if (g_matching2_thread.joinable()) {
return;
}
g_matching2_stop.store(false, std::memory_order_relaxed);
g_matching2_thread = std::thread(Matching2HandshakeThreadMain);
}
void StopMatching2HandshakeThread() {
{
std::lock_guard lock(g_matching2_thread_mutex);
g_matching2_stop.store(true, std::memory_order_relaxed);
}
if (g_matching2_thread.joinable()) {
g_matching2_thread.join();
}
}
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
return 0;
}
u64 total_ping = 0;
u32 ping_count = 0;
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == ctx.my_member_id) {
continue;
}
const auto peer_it = ctx.peers.find(member_id);
if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) {
continue;
}
total_ping += peer_it->second.ping_us;
++ping_count;
}
return ping_count == 0 ? 0 : static_cast<u32>(total_ping / ping_count);
}
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
CallbackPayload& p =
ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload;
p.Reset();
const auto room_it = ctx.room_cache.find(roomId);
p.ping_info_response = std::make_unique<OrbisNpMatching2SignalingGetPingInfoResponse>();
auto& out = *p.ping_info_response;
out = {};
if (room_it != ctx.room_cache.end()) {
out.serverId = room_it->second.server_id;
out.worldId = room_it->second.world_id;
out.roomId = room_it->second.room_id;
} else {
out.serverId = ctx.server_id;
out.worldId = ctx.world_id;
out.roomId = roomId;
}
out.rtt = GetRoomPingUs(ctx, roomId);
p.request_data = p.ping_info_response.get();
return p.request_data;
}
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant) {
if (!connInfo) {
LOG_ERROR(Lib_NpMatching2, "connInfo null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId);
if (a_variant) {
*static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo) = {};
} else {
*static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo) = {};
}
return ORBIS_OK;
}
const auto member_it = room_it->second.members.find(memberId);
const MemberCache* member =
member_it != room_it->second.members.end() ? &member_it->second : nullptr;
const auto peer_it = ctx.peers.find(memberId);
const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr;
if (a_variant) {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDRESS_A:
if (member) {
out->peerAddrA.accountId = member->account_id;
out->peerAddrA.platform = member->platform;
}
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info A type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
} else {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NP_ID:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NPID:
if (member) {
out->npId = member->np_id;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
}
LOG_INFO(Lib_NpMatching2, "connection info{}: ctx={} room={} member={} type={} rtt={}",
a_variant ? "A" : "", ctx.ctx_id, roomId, memberId, infoType,
peer ? peer->ping_us : 0);
return ORBIS_OK;
}
} // namespace Libraries::Np::NpMatching2
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
namespace Libraries::Np::NpMatching2 {
struct ContextObject;
void StartMatching2HandshakeThread();
void StopMatching2HandshakeThread();
bool SendMatching2StunPing(const ContextObject& ctx);
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id);
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code);
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code);
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId);
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId);
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant);
} // namespace Libraries::Np::NpMatching2
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_partner_error.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpPartner {
static bool g_library_init = false;
std::mutex g_library_mutex{};
s32 PS4_SYSV_ABI sceNpEAAccessTerminate() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
std::scoped_lock lk{g_library_mutex};
g_library_init = false;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpHasEAAccessSubscriptionAbortRequest() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
// Request logic is unimplemented, so this does nothing.
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpEAAccessInitialize() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
g_library_init = true;
// Also retrieves and sends compiled SDK version to server.
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpHasEAAccessSubscription(UserService::OrbisUserServiceUserId user_id,
bool* result) {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
if (result == nullptr) {
return ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_library_mutex};
// In the real library, this creates and sends a request that checks for EA subscription,
// then waits for the request to return a response, and returns that response.
// NP signed out likely returns an error, but I haven't figured out the error code yet.
// For now, stub having no subscription.
*result = false;
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("pMxXhNozUX8", "libSceNpPartner001", 1, "libSceNpPartner001",
sceNpEAAccessTerminate);
LIB_FUNCTION("pQfYTZHznMc", "libSceNpPartner001", 1, "libSceNpPartner001",
sceNpHasEAAccessSubscriptionAbortRequest);
LIB_FUNCTION("7CxI50-xlCk", "libSceNpPartner001", 1, "libSceNpPartner001",
sceNpEAAccessInitialize);
LIB_FUNCTION("+OnbUs1CV0M", "libSceNpPartner001", 1, "libSceNpPartner001",
sceNpHasEAAccessSubscription);
};
} // namespace Libraries::Np::NpPartner
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpPartner {
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpPartner
@@ -0,0 +1,9 @@
// 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_PARTNER_ERROR_NOT_INITIALIZED = 0x819d0001;
constexpr int ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT = 0x819d0002;
+186
View File
@@ -0,0 +1,186 @@
// 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_error.h"
#include "core/libraries/np/np_party.h"
namespace Libraries::Np::NpParty {
s32 PS4_SYSV_ABI sceNpPartyCheckCallback() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyCreate() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyCreateA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetId() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetMemberInfo() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY;
}
s32 PS4_SYSV_ABI sceNpPartyGetMemberInfoA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY;
}
s32 PS4_SYSV_ABI sceNpPartyGetMembers() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY;
}
s32 PS4_SYSV_ABI sceNpPartyGetMembersA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_NP_PARTY_ERROR_NOT_IN_PARTY;
}
s32 PS4_SYSV_ABI sceNpPartyGetMemberSessionInfo() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetMemberVoiceInfo() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetState(OrbisNpPartyState* state) {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
*state = OrbisNpPartyState::NotInParty;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetStateAsUser() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetStateAsUserA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyGetVoiceChatPriority() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyInitialize() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyJoin() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyLeave() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyRegisterHandler() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyRegisterHandlerA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyRegisterPrivateHandler() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartySendBinaryMessage() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartySetVoiceChatPriority() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyShowInvitationList() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyShowInvitationListA() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyTerminate() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpPartyUnregisterPrivateHandler() {
LOG_ERROR(Lib_NpParty, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("3e4k2mzLkmc", "libSceNpParty", 1, "libSceNpParty", sceNpPartyCheckCallback);
LIB_FUNCTION("nOZRy-slBoA", "libSceNpParty", 1, "libSceNpParty", sceNpPartyCreate);
LIB_FUNCTION("XQSUbbnpPBA", "libSceNpParty", 1, "libSceNpParty", sceNpPartyCreateA);
LIB_FUNCTION("DRA3ay-1DFQ", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetId);
LIB_FUNCTION("F1P+-wpxQow", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetMemberInfo);
LIB_FUNCTION("v2RYVGrJDkM", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetMemberInfoA);
LIB_FUNCTION("T2UOKf00ZN0", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetMembers);
LIB_FUNCTION("TaNw7W25QJw", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetMembersA);
LIB_FUNCTION("4gOMfNYzllw", "libSceNpParty", 1, "libSceNpParty",
sceNpPartyGetMemberSessionInfo);
LIB_FUNCTION("EKi1jx59SP4", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetMemberVoiceInfo);
LIB_FUNCTION("aEzKdJzATZ0", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetState);
LIB_FUNCTION("o7grRhiGHYI", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetStateAsUser);
LIB_FUNCTION("EjyAI+QNgFw", "libSceNpParty", 1, "libSceNpParty", sceNpPartyGetStateAsUserA);
LIB_FUNCTION("-lc6XZnQXvM", "libSceNpParty", 1, "libSceNpParty",
sceNpPartyGetVoiceChatPriority);
LIB_FUNCTION("lhYCTQmBkds", "libSceNpParty", 1, "libSceNpParty", sceNpPartyInitialize);
LIB_FUNCTION("RXNCDw2GDEg", "libSceNpParty", 1, "libSceNpParty", sceNpPartyJoin);
LIB_FUNCTION("J8jAi-tfJHc", "libSceNpParty", 1, "libSceNpParty", sceNpPartyLeave);
LIB_FUNCTION("kA88gbv71ao", "libSceNpParty", 1, "libSceNpParty", sceNpPartyRegisterHandler);
LIB_FUNCTION("+v4fVHMwFWc", "libSceNpParty", 1, "libSceNpParty", sceNpPartyRegisterHandlerA);
LIB_FUNCTION("zo4G5WWYpKg", "libSceNpParty", 1, "libSceNpParty",
sceNpPartyRegisterPrivateHandler);
LIB_FUNCTION("U6VdUe-PNAY", "libSceNpParty", 1, "libSceNpParty", sceNpPartySendBinaryMessage);
LIB_FUNCTION("nazKyHygHhY", "libSceNpParty", 1, "libSceNpParty",
sceNpPartySetVoiceChatPriority);
LIB_FUNCTION("-MFiL7hEnPE", "libSceNpParty", 1, "libSceNpParty", sceNpPartyShowInvitationList);
LIB_FUNCTION("yARHEYLajs0", "libSceNpParty", 1, "libSceNpParty", sceNpPartyShowInvitationListA);
LIB_FUNCTION("oLYkibiHqRA", "libSceNpParty", 1, "libSceNpParty", sceNpPartyTerminate);
LIB_FUNCTION("zQ7gIvt11Pc", "libSceNpParty", 1, "libSceNpParty",
sceNpPartyUnregisterPrivateHandler);
LIB_FUNCTION("nOZRy-slBoA", "libSceNpPartyCompat", 1, "libSceNpParty", sceNpPartyCreate);
LIB_FUNCTION("F1P+-wpxQow", "libSceNpPartyCompat", 1, "libSceNpParty", sceNpPartyGetMemberInfo);
LIB_FUNCTION("T2UOKf00ZN0", "libSceNpPartyCompat", 1, "libSceNpParty", sceNpPartyGetMembers);
LIB_FUNCTION("o7grRhiGHYI", "libSceNpPartyCompat", 1, "libSceNpParty",
sceNpPartyGetStateAsUser);
LIB_FUNCTION("kA88gbv71ao", "libSceNpPartyCompat", 1, "libSceNpParty",
sceNpPartyRegisterHandler);
LIB_FUNCTION("-MFiL7hEnPE", "libSceNpPartyCompat", 1, "libSceNpParty",
sceNpPartyShowInvitationList);
};
} // namespace Libraries::Np::NpParty
+47
View File
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpParty {
enum class OrbisNpPartyState : u16 {
NotInParty = 2,
InPrivateParty = 3,
};
s32 PS4_SYSV_ABI sceNpPartyCheckCallback();
s32 PS4_SYSV_ABI sceNpPartyCreate();
s32 PS4_SYSV_ABI sceNpPartyCreateA();
s32 PS4_SYSV_ABI sceNpPartyGetId();
s32 PS4_SYSV_ABI sceNpPartyGetMemberInfo();
s32 PS4_SYSV_ABI sceNpPartyGetMemberInfoA();
s32 PS4_SYSV_ABI sceNpPartyGetMembers();
s32 PS4_SYSV_ABI sceNpPartyGetMembersA();
s32 PS4_SYSV_ABI sceNpPartyGetMemberSessionInfo();
s32 PS4_SYSV_ABI sceNpPartyGetMemberVoiceInfo();
s32 PS4_SYSV_ABI sceNpPartyGetState(OrbisNpPartyState* state);
s32 PS4_SYSV_ABI sceNpPartyGetStateAsUser();
s32 PS4_SYSV_ABI sceNpPartyGetStateAsUserA();
s32 PS4_SYSV_ABI sceNpPartyGetVoiceChatPriority();
s32 PS4_SYSV_ABI sceNpPartyInitialize();
s32 PS4_SYSV_ABI sceNpPartyJoin();
s32 PS4_SYSV_ABI sceNpPartyLeave();
s32 PS4_SYSV_ABI sceNpPartyRegisterHandler();
s32 PS4_SYSV_ABI sceNpPartyRegisterHandlerA();
s32 PS4_SYSV_ABI sceNpPartyRegisterPrivateHandler();
s32 PS4_SYSV_ABI sceNpPartySendBinaryMessage();
s32 PS4_SYSV_ABI sceNpPartySetVoiceChatPriority();
s32 PS4_SYSV_ABI sceNpPartyShowInvitationList();
s32 PS4_SYSV_ABI sceNpPartyShowInvitationListA();
s32 PS4_SYSV_ABI sceNpPartyTerminate();
s32 PS4_SYSV_ABI sceNpPartyUnregisterPrivateHandler();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpParty
@@ -0,0 +1,201 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <core/libraries/system/commondialog.h>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "magic_enum/magic_enum.hpp"
#include "np_profile_dialog.h"
#include "np_profile_dialog_ui.h"
namespace Libraries::Np::NpProfileDialog {
static auto g_status = Libraries::CommonDialog::Status::NONE;
static NpProfileDialogState g_state{};
static OrbisNpProfileDialogResult g_result{};
static NpProfileDialogUi g_profile_dialog_ui;
Libraries::CommonDialog::Error sceNpProfileDialogOpen(OrbisNpProfileDialogParam* param) {
if (g_status != Libraries::CommonDialog::Status::INITIALIZED &&
g_status != Libraries::CommonDialog::Status::FINISHED) {
LOG_ERROR(Lib_NpProfileDialog, "called without initialize");
return Libraries::CommonDialog::Error::INVALID_STATE;
}
if (param == nullptr) {
LOG_ERROR(Lib_NpProfileDialog, "param is nullptr");
return Libraries::CommonDialog::Error::ARG_NULL;
}
LOG_DEBUG(Lib_NpProfileDialog,
"param details: mode={}, userId={}, targetOnlineId='{}', userData={}",
static_cast<int>(param->mode), param->userId, param->targetOnlineId.data,
fmt::ptr(param->userData));
NpProfileDialogState state{};
state.onlineId = std::string(param->targetOnlineId.data);
state.hasAccountId = false;
state.mode = param->mode;
state.userId = param->userId;
g_state = state;
g_result = {};
g_result.userData = param->userData;
g_status = Libraries::CommonDialog::Status::RUNNING;
LOG_DEBUG(Lib_NpProfileDialog, "creating UI with onlineId='{}', mode={}, userId={}",
state.onlineId, static_cast<int>(state.mode), state.userId);
g_profile_dialog_ui = NpProfileDialogUi(&g_state, &g_status, &g_result);
LOG_INFO(Lib_NpProfileDialog, "dialog opened successfully for user {}", param->userId);
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogClose() {
LOG_DEBUG(Lib_NpProfileDialog, "called");
if (g_status == Libraries::CommonDialog::Status::NONE) {
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
}
if (g_status != Libraries::CommonDialog::Status::RUNNING) {
return Libraries::CommonDialog::Error::NOT_RUNNING;
}
g_profile_dialog_ui.Finish(Libraries::CommonDialog::Result::OK);
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceNpProfileDialogGetResult(OrbisNpProfileDialogResult* result) {
LOG_DEBUG(Lib_NpProfileDialog, "called");
if (g_status == Libraries::CommonDialog::Status::NONE) {
LOG_ERROR(Lib_NpProfileDialog, "failed: not initialized (g_status=NONE)");
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
}
if (result == nullptr) {
LOG_ERROR(Lib_NpProfileDialog, "failed: result pointer is nullptr");
return Libraries::CommonDialog::Error::ARG_NULL;
}
if (g_status != Libraries::CommonDialog::Status::FINISHED) {
LOG_ERROR(Lib_NpProfileDialog, "failed: dialog not finished (g_status={})",
static_cast<int>(g_status));
return Libraries::CommonDialog::Error::NOT_FINISHED;
}
*result = g_result;
LOG_DEBUG(Lib_NpProfileDialog, "result details: resultCode={}, userAction={}, userData={}",
g_result.result, static_cast<int>(g_result.userAction), fmt::ptr(g_result.userData));
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Status PS4_SYSV_ABI sceNpProfileDialogGetStatus() {
LOG_TRACE(Lib_NpProfileDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogInitialize() {
if (!CommonDialog::g_isInitialized) {
LOG_ERROR(Lib_NpProfileDialog, "failed: system not initialized");
return Libraries::CommonDialog::Error::NOT_SYSTEM_INITIALIZED;
}
if (g_status != Libraries::CommonDialog::Status::NONE) {
LOG_ERROR(Lib_NpProfileDialog, "failed: already initialized (g_status={})",
static_cast<int>(g_status));
return Libraries::CommonDialog::Error::ALREADY_INITIALIZED;
}
if (CommonDialog::g_isUsed) {
LOG_ERROR(Lib_NpProfileDialog, "failed: dialog system busy (g_isUsed=true)");
return Libraries::CommonDialog::Error::BUSY;
}
g_status = Libraries::CommonDialog::Status::INITIALIZED;
CommonDialog::g_isUsed = true;
LOG_INFO(Lib_NpProfileDialog, "initialized successfully");
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceNpProfileDialogOpenA(OrbisNpProfileDialogParamA* param) {
if (g_status != Libraries::CommonDialog::Status::INITIALIZED &&
g_status != Libraries::CommonDialog::Status::FINISHED) {
LOG_ERROR(Lib_NpProfileDialog, "called without initialize");
return Libraries::CommonDialog::Error::INVALID_STATE;
}
if (param == nullptr) {
LOG_ERROR(Lib_NpProfileDialog, "failed: param is nullptr");
return Libraries::CommonDialog::Error::ARG_NULL;
}
LOG_DEBUG(Lib_NpProfileDialog,
"param details: mode={}, userId={}, targetAccountId='{}', userData={}",
static_cast<int>(param->mode), param->userId, param->targetAccountId,
fmt::ptr(param->userData));
LOG_ERROR(Lib_NpProfileDialog, "(STUBBED) called");
NpProfileDialogState state{};
state.accountId = param->targetAccountId;
state.hasAccountId = true;
state.mode = param->mode;
state.userId = param->userId;
g_state = state;
g_result = {};
g_result.userData = param->userData;
g_status = Libraries::CommonDialog::Status::RUNNING;
LOG_DEBUG(Lib_NpProfileDialog, "creating UI with accountId='{}', mode={}, userId={}",
state.accountId, static_cast<int>(state.mode), state.userId);
g_profile_dialog_ui = NpProfileDialogUi(&g_state, &g_status, &g_result);
LOG_INFO(Lib_NpProfileDialog, "dialog opened successfully for user {} (account ID variant)",
param->userId);
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogTerminate() {
LOG_DEBUG(Lib_NpProfileDialog, "called (g_status={})", static_cast<int>(g_status));
if (g_status == Libraries::CommonDialog::Status::RUNNING) {
LOG_ERROR(Lib_NpProfileDialog, "dialog is running, closing it first");
sceNpProfileDialogClose();
}
if (g_status == Libraries::CommonDialog::Status::NONE) {
LOG_ERROR(Lib_NpProfileDialog, "failed: not initialized (g_status=NONE)");
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
}
g_status = Libraries::CommonDialog::Status::NONE;
CommonDialog::g_isUsed = false;
LOG_INFO(Lib_NpProfileDialog, "terminated successfully");
return Libraries::CommonDialog::Error::OK;
}
Libraries::CommonDialog::Status PS4_SYSV_ABI sceNpProfileDialogUpdateStatus() {
LOG_TRACE(Lib_NpProfileDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("uj9Cz7Tk0cc", "libSceNpProfileDialogCompat", 1, "libSceNpProfileDialog",
sceNpProfileDialogOpen);
LIB_FUNCTION("wkwjz0Xdo2A", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogClose);
LIB_FUNCTION("8rhLl1-0W-o", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogGetResult);
LIB_FUNCTION("3BqoiFOjSsk", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogGetStatus);
LIB_FUNCTION("Lg+NCE6pTwQ", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogInitialize);
LIB_FUNCTION("uj9Cz7Tk0cc", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogOpen);
LIB_FUNCTION("nrQRlLKzdwE", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogOpenA);
LIB_FUNCTION("0Sp9vJcB1-w", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogTerminate);
LIB_FUNCTION("haVZE9FgKqE", "libSceNpProfileDialog", 1, "libSceNpProfileDialog",
sceNpProfileDialogUpdateStatus);
};
} // namespace Libraries::Np::NpProfileDialog
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/commondialog.h>
#include <core/libraries/system/userservice.h>
#include "common/types.h"
#include "core/libraries/np/np_types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpProfileDialog {
enum class OrbisNpProfileDialogMode : u32 {
ORBIS_NP_PROFILE_DIALOG_MODE_INVALID = 0,
ORBIS_NP_PROFILE_DIALOG_MODE_NORMAL = 1,
ORBIS_NP_PROFILE_DIALOG_MODE_FRIEND_REQUEST = 2,
ORBIS_NP_PROFILE_DIALOG_MODE_ADD_TO_BLOCK_LIST = 3,
ORBIS_NP_PROFILE_DIALOG_MODE_GRIEF_REPORT = 4,
};
static constexpr s32 ORBIS_NP_PROFILE_DIALOG_MENU_GRIEF_REPORT_ITEM_INVALID = 0x00000000;
static constexpr s32 ORBIS_NP_PROFILE_DIALOG_MENU_GRIEF_REPORT_ITEM_ONLINE_ID = 0x00000001;
static constexpr s32 ORBIS_NP_PROFILE_DIALOG_MENU_GRIEF_REPORT_ITEM_NAME = 0x00000002;
static constexpr s32 ORBIS_NP_PROFILE_DIALOG_MENU_GRIEF_REPORT_ITEM_PICTURE = 0x00000004;
static constexpr s32 ORBIS_NP_PROFILE_DIALOG_MENU_GRIEF_REPORT_ITEM_ABOUT_ME = 0x00000008;
struct OrbisNpProfileDialogParam {
CommonDialog::BaseParam baseParam;
u64 size;
OrbisNpProfileDialogMode mode;
Libraries::UserService::OrbisUserServiceUserId userId;
Libraries::Np::OrbisNpOnlineId targetOnlineId;
void* userData;
u8 reserved[32];
};
struct OrbisNpProfileGriefReportParam {
s32 reportItem;
u8 reserved[28];
};
struct OrbisNpProfileDialogParamA {
CommonDialog::BaseParam baseParam;
u64 size;
OrbisNpProfileDialogMode mode;
Libraries::UserService::OrbisUserServiceUserId userId;
Libraries::Np::OrbisNpAccountId targetAccountId;
int : 32;
void* userData;
union {
uint8_t reserved[32];
OrbisNpProfileGriefReportParam griefReportParam;
};
};
struct OrbisNpProfileDialogResult {
s32 result;
CommonDialog::Result userAction;
void* userData;
u8 reserved[32];
};
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceNpProfileDialogOpen(OrbisNpProfileDialogParam* param);
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogClose();
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceNpProfileDialogGetResult(OrbisNpProfileDialogResult* result);
Libraries::CommonDialog::Status PS4_SYSV_ABI sceNpProfileDialogGetStatus();
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogInitialize();
Libraries::CommonDialog::Error PS4_SYSV_ABI
sceNpProfileDialogOpenA(OrbisNpProfileDialogParamA* param);
Libraries::CommonDialog::Error PS4_SYSV_ABI sceNpProfileDialogTerminate();
Libraries::CommonDialog::Status PS4_SYSV_ABI sceNpProfileDialogUpdateStatus();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpProfileDialog
@@ -0,0 +1,279 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <cmath>
#include <thread>
#include <utility>
#include <imgui.h>
#include <imgui/imgui_std.h>
#include "np_profile_dialog_ui.h"
using namespace ImGui;
using namespace Libraries::CommonDialog;
namespace Libraries::Np::NpProfileDialog {
// PS4 color palette
static constexpr ImVec4 COL_OVERLAY = {0.00f, 0.00f, 0.00f, 0.65f};
static constexpr ImVec4 COL_HEADER_TOP = {0.00f, 0.34f, 0.62f, 1.00f};
static constexpr ImVec4 COL_HEADER_BOT = {0.00f, 0.22f, 0.42f, 1.00f};
static constexpr ImVec4 COL_CARD_BG = {0.11f, 0.11f, 0.12f, 1.00f};
static constexpr ImVec4 COL_SEPARATOR = {0.22f, 0.22f, 0.24f, 1.00f};
static constexpr ImVec4 COL_AVATAR_BG = {0.18f, 0.18f, 0.20f, 1.00f};
static constexpr ImVec4 COL_AVATAR_OUTLINE = {0.00f, 0.44f, 0.75f, 1.00f};
static constexpr ImVec4 COL_TEXT_PRIMARY = {1.00f, 1.00f, 1.00f, 1.00f};
static constexpr ImVec4 COL_TEXT_SECONDARY = {0.65f, 0.65f, 0.68f, 1.00f};
static constexpr ImVec4 COL_BUTTON_NORMAL = {0.18f, 0.18f, 0.20f, 1.00f};
static constexpr ImVec4 COL_BUTTON_HOVERED = {0.00f, 0.44f, 0.75f, 1.00f};
static constexpr ImVec4 COL_BUTTON_ACTIVE = {0.00f, 0.33f, 0.60f, 1.00f};
static constexpr ImVec4 COL_BUTTON_BORDER = {0.32f, 0.32f, 0.35f, 1.00f};
static constexpr float CARD_WIDTH = 480.0f;
static constexpr float CARD_HEIGHT = 330.0f;
static constexpr float HEADER_HEIGHT = 44.0f;
static constexpr float FOOTER_HEIGHT = 56.0f;
static constexpr float AVATAR_RADIUS = 44.0f;
static constexpr float AVATAR_REL_X = CARD_WIDTH * 0.5f;
static constexpr float AVATAR_REL_Y = HEADER_HEIGHT + AVATAR_RADIUS + 16.0f;
static constexpr float FADE_SPEED = 8.0f;
static constexpr ImVec2 CLOSE_BUTTON_SIZE = {110.0f, 34.0f};
static ImU32 AlphaScale(ImVec4 col, float alpha) {
col.w *= alpha;
return GetColorU32(col);
}
static void DrawCardShadow(ImDrawList* dl, ImVec2 min, ImVec2 max, float alpha) {
for (int i = 0; i < 6; ++i) {
const float spread = (float)(i + 1) * 3.5f;
const float a = alpha * (0.28f - (float)i * 0.04f);
if (a <= 0.0f)
break;
dl->AddRectFilled({min.x - spread, min.y + spread}, {max.x + spread, max.y + spread * 1.5f},
IM_COL32(0, 0, 0, (int)(a * 255.0f)), 8.0f + spread);
}
}
static void DrawAvatarPlaceholder(ImDrawList* dl, ImVec2 center, float radius, float alpha) {
const ImU32 bg_col = AlphaScale(COL_AVATAR_BG, alpha);
const ImU32 outline_col = AlphaScale(COL_AVATAR_OUTLINE, alpha);
const ImU32 silhouette = IM_COL32(90, 90, 96, (int)(alpha * 255.0f));
dl->AddCircleFilled(center, radius, bg_col, 64);
dl->AddCircleFilled({center.x, center.y - radius * 0.18f}, radius * 0.32f, silhouette, 32);
dl->AddCircleFilled({center.x, center.y + radius * 0.45f}, radius * 0.55f, silhouette, 48);
// Outline drawn last — sits on top of silhouette
dl->AddCircle(center, radius, outline_col, 64, 2.5f);
}
static const char* GetModeTitle(OrbisNpProfileDialogMode mode) {
switch (mode) {
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_NORMAL:
return "Profile";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_FRIEND_REQUEST:
return "Friend Request";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_ADD_TO_BLOCK_LIST:
return "Block List";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_GRIEF_REPORT:
return "Report";
default:
return "Profile";
}
}
static const char* GetModeSubtitle(OrbisNpProfileDialogMode mode) {
switch (mode) {
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_NORMAL:
return "Player Profile";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_FRIEND_REQUEST:
return "Send Friend Request";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_ADD_TO_BLOCK_LIST:
return "Add to Block List";
case OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_GRIEF_REPORT:
return "Report Player";
default:
return "Player Profile";
}
}
NpProfileDialogUi::NpProfileDialogUi(NpProfileDialogState* state, Status* status,
OrbisNpProfileDialogResult* result)
: state(state), status(status), result(result) {
if (status && *status == Status::RUNNING) {
first_render = true;
open_alpha = 0.0f;
AddLayer(this);
}
}
NpProfileDialogUi::~NpProfileDialogUi() {
Finish(Result::USER_CANCELED);
}
NpProfileDialogUi::NpProfileDialogUi(NpProfileDialogUi&& other) noexcept
: Layer(other), state(other.state), status(other.status), result(other.result),
open_alpha(other.open_alpha) {
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
other.open_alpha = 0.0f;
}
NpProfileDialogUi& NpProfileDialogUi::operator=(NpProfileDialogUi other) {
using std::swap;
swap(state, other.state);
swap(status, other.status);
swap(result, other.result);
swap(open_alpha, other.open_alpha);
if (status && *status == Status::RUNNING) {
first_render = true;
open_alpha = 0.0f;
AddLayer(this);
}
return *this;
}
void NpProfileDialogUi::Finish(Result user_action) {
if (result) {
result->result = 0; // ORBIS_OK
result->userAction = user_action;
}
if (status) {
*status = Status::FINISHED;
}
state = nullptr;
status = nullptr;
result = nullptr;
RemoveLayer(this);
}
void NpProfileDialogUi::Draw() {
if (status == nullptr || *status != Status::RUNNING) {
return;
}
const auto& io = GetIO();
open_alpha = std::fmin(open_alpha + io.DeltaTime * FADE_SPEED, 1.0f);
const float card_x = (io.DisplaySize.x - CARD_WIDTH) * 0.5f;
const float card_y = (io.DisplaySize.y - CARD_HEIGHT) * 0.5f;
const ImVec2 card_min = {card_x, card_y};
const ImVec2 card_max = {card_x + CARD_WIDTH, card_y + CARD_HEIGHT};
SetNextWindowPos({0, 0});
SetNextWindowSize(io.DisplaySize);
PushStyleColor(ImGuiCol_WindowBg, AlphaScale(COL_OVERLAY, open_alpha));
PushStyleVar(ImGuiStyleVar_WindowPadding, {0.0f, 0.0f});
Begin("##NpProfileDialogOverlay", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing);
End();
PopStyleVar();
PopStyleColor();
SetNextWindowPos(card_min);
SetNextWindowSize({CARD_WIDTH, CARD_HEIGHT});
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
KeepNavHighlight();
PushStyleColor(ImGuiCol_WindowBg, AlphaScale(COL_CARD_BG, open_alpha));
PushStyleColor(ImGuiCol_Border, AlphaScale(COL_SEPARATOR, open_alpha));
PushStyleColor(ImGuiCol_Text, AlphaScale(COL_TEXT_PRIMARY, open_alpha));
PushStyleColor(ImGuiCol_Button, AlphaScale(COL_BUTTON_NORMAL, open_alpha));
PushStyleColor(ImGuiCol_ButtonHovered, AlphaScale(COL_BUTTON_HOVERED, open_alpha));
PushStyleColor(ImGuiCol_ButtonActive, AlphaScale(COL_BUTTON_ACTIVE, open_alpha));
PushStyleVar(ImGuiStyleVar_WindowPadding, {0.0f, 0.0f});
PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
if (Begin("##NpProfileDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoMove)) {
ImDrawList* dl = GetWindowDrawList();
const ImVec2 win_pos = GetWindowPos();
DrawCardShadow(dl, card_min, card_max, open_alpha);
const ImVec2 hdr_min = win_pos;
const ImVec2 hdr_max = {win_pos.x + CARD_WIDTH, win_pos.y + HEADER_HEIGHT};
dl->AddRectFilledMultiColor(hdr_min, hdr_max, AlphaScale(COL_HEADER_TOP, open_alpha),
AlphaScale(COL_HEADER_TOP, open_alpha),
AlphaScale(COL_HEADER_BOT, open_alpha),
AlphaScale(COL_HEADER_BOT, open_alpha));
dl->AddRectFilled({hdr_min.x, hdr_max.y - 6.0f}, hdr_max,
AlphaScale(COL_HEADER_BOT, open_alpha));
dl->AddLine({hdr_min.x + 6.0f, hdr_min.y + 1.0f}, {hdr_max.x - 6.0f, hdr_min.y + 1.0f},
IM_COL32(255, 255, 255, (int)(open_alpha * 40.0f)), 1.0f);
// Footer separator
const float footer_top_screen = win_pos.y + CARD_HEIGHT - FOOTER_HEIGHT;
dl->AddLine({win_pos.x, footer_top_screen}, {win_pos.x + CARD_WIDTH, footer_top_screen},
AlphaScale(COL_SEPARATOR, open_alpha), 1.0f);
// Avatar
const ImVec2 avatar_screen = {win_pos.x + AVATAR_REL_X, win_pos.y + AVATAR_REL_Y};
DrawAvatarPlaceholder(dl, avatar_screen, AVATAR_RADIUS, open_alpha);
// Header title
const char* title = GetModeTitle(state->mode);
const ImVec2 title_sz = CalcTextSize(title);
SetCursorPos({(CARD_WIDTH - title_sz.x) * 0.5f, (HEADER_HEIGHT - title_sz.y) * 0.5f});
TextUnformatted(title);
// Online ID / Account ID
char id_buf[32]{};
const char* id_str = state->onlineId.c_str();
if (state->hasAccountId) {
snprintf(id_buf, sizeof(id_buf), "%" PRIu64, state->accountId);
id_str = id_buf;
}
const float id_y = AVATAR_REL_Y + AVATAR_RADIUS + 14.0f;
const ImVec2 id_sz = CalcTextSize(id_str);
SetCursorPos({(CARD_WIDTH - id_sz.x) * 0.5f, id_y});
TextUnformatted(id_str);
// Mode subtitle
PushStyleColor(ImGuiCol_Text, AlphaScale(COL_TEXT_SECONDARY, open_alpha));
const char* subtitle = GetModeSubtitle(state->mode);
const ImVec2 sub_sz = CalcTextSize(subtitle);
SetCursorPos({(CARD_WIDTH - sub_sz.x) * 0.5f, id_y + id_sz.y + 5.0f});
TextUnformatted(subtitle);
PopStyleColor();
const float footer_y_local = CARD_HEIGHT - FOOTER_HEIGHT;
PushStyleColor(ImGuiCol_Border, AlphaScale(COL_BUTTON_BORDER, open_alpha));
PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
SetCursorPos({CARD_WIDTH - CLOSE_BUTTON_SIZE.x - 16.0f,
footer_y_local + (FOOTER_HEIGHT - CLOSE_BUTTON_SIZE.y) * 0.5f});
if (Button("Close##btn", CLOSE_BUTTON_SIZE)) {
Finish(Result::USER_CANCELED);
}
PopStyleVar();
PopStyleColor();
if (first_render) {
SetItemCurrentNavFocus();
}
}
End();
PopStyleVar(4);
PopStyleColor(6);
first_render = false;
}
} // namespace Libraries::Np::NpProfileDialog
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <variant>
#include "common/fixed_value.h"
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
#include "np_profile_dialog.h"
namespace Libraries::Np::NpProfileDialog {
struct NpProfileDialogState {
std::string onlineId;
OrbisNpAccountId accountId{};
bool hasAccountId{false};
OrbisNpProfileDialogMode mode{OrbisNpProfileDialogMode::ORBIS_NP_PROFILE_DIALOG_MODE_INVALID};
int userId{};
};
class NpProfileDialogUi : public ImGui::Layer {
public:
explicit NpProfileDialogUi(NpProfileDialogState* state = nullptr,
CommonDialog::Status* status = nullptr,
OrbisNpProfileDialogResult* result = nullptr);
~NpProfileDialogUi() override;
NpProfileDialogUi(const NpProfileDialogUi& other) = delete;
NpProfileDialogUi(NpProfileDialogUi&& other) noexcept;
NpProfileDialogUi& operator=(NpProfileDialogUi other);
void Finish(CommonDialog::Result user_action);
void Draw() override;
private:
NpProfileDialogState* state{};
CommonDialog::Status* status{};
OrbisNpProfileDialogResult* result{};
bool first_render{false};
float open_alpha{0.0f};
};
} // namespace Libraries::Np::NpProfileDialog
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,410 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/rtc/rtc.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpScore {
using OrbisNpScoreTitleCtxId = s32;
using OrbisNpScoreRequestId = s32;
using OrbisNpScoreBoardId = u32;
using OrbisNpScorePcId = s32;
using OrbisNpScoreRankNumber = u32;
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;
u32 updateMode;
u32 sortMode;
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;
u32* startSerialRank;
u32* hits;
};
struct OrbisNpScoreAccountIdPcId {
OrbisNpAccountId accountId;
OrbisNpScorePcId pcId;
u8 pad[4];
};
struct OrbisNpScoreNpIdPcId {
OrbisNpId npId;
OrbisNpScorePcId pcId;
u8 pad[4];
};
struct OrbisNpScoreRankData {
OrbisNpId npId;
u8 reserved[49];
u8 pad0[3];
OrbisNpScorePcId pcId;
OrbisNpScoreRankNumber serialRank;
OrbisNpScoreRankNumber rank;
OrbisNpScoreRankNumber highestRank;
s32 hasGameData;
u8 pad1[4];
OrbisNpScoreValue scoreValue;
Rtc::OrbisRtcTick recordDate;
};
static_assert(sizeof(OrbisNpScoreRankData) == 0x80, "OrbisNpScoreRankData must be 128 bytes");
struct OrbisNpScoreRankDataA {
OrbisNpOnlineId onlineId;
u8 reserved0[16];
u8 reserved[49];
u8 pad0[3];
OrbisNpScorePcId pcId;
OrbisNpScoreRankNumber serialRank;
OrbisNpScoreRankNumber rank;
OrbisNpScoreRankNumber highestRank;
s32 hasGameData;
u8 pad1[4];
OrbisNpScoreValue scoreValue;
Rtc::OrbisRtcTick recordDate;
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;
u8 reserved[49];
u8 pad0[3];
OrbisNpScorePcId pcId;
OrbisNpScoreRankNumber serialRank;
OrbisNpScoreRankNumber rank;
OrbisNpScoreRankNumber highestRank;
s32 hasGameData;
u8 pad1[4];
OrbisNpScoreValue scoreValue;
Rtc::OrbisRtcTick recordDate;
OrbisNpAccountId accountId;
u8 pad2[8];
};
struct OrbisNpScorePlayerRankDataForCrossSave {
s32 hasData;
u8 pad0[4];
OrbisNpScoreRankDataForCrossSave rankData;
};
//***********************************
// Title context management functions
//***********************************
int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel,
const OrbisNpId* selfNpId);
int PS4_SYSV_ABI sceNpScoreCreateNpTitleCtxA(OrbisNpServiceLabel npServiceLabel,
UserService::OrbisUserServiceUserId selfId);
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,
u64 commentArraySize, OrbisNpScoreGameInfo* infoArray,
u64 infoArraySize, u64 arrayNum,
Rtc::OrbisRtcTick* lastSortDate,
OrbisNpScoreRankNumber* totalRecord,
OrbisNpScoreGetFriendRankingOptParam* option);
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);
//***********************************
// 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,
OrbisNpScoreComment* commentArray, u64 commentArraySize, OrbisNpScoreGameInfo* infoArray,
u64 infoArraySize, u64 arrayNum, Rtc::OrbisRtcTick* lastSortDate,
OrbisNpScoreRankNumber* totalRecord, OrbisNpScoreGetFriendRankingOptParam* option);
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);
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);
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);
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);
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);
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);
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);
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);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpScore
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <optional>
#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<s32> 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
@@ -0,0 +1,905 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <vector>
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/net_util.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
namespace Libraries::Np::NpSignaling {
using Libraries::Net::sceNetNtohs;
s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s32 cpuAffinityMask,
s64 threadStackSize) {
if (g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_ALREADY_INITIALIZED;
}
InitSignalingMutex();
SignalingMutexGuard lock;
u32 app_type_4 = 0;
const s32 app_type_rc = Helpers::CheckInitializeAppType(&app_type_4);
if (app_type_rc < 0) {
return app_type_rc;
}
if (app_type_4 != 0) {
return ORBIS_NP_SIGNALING_ERROR_PROHIBITED_TO_USE;
}
if (memorySize == 0) {
memorySize = 0x20000;
}
if (threadPriority == 0) {
threadPriority = 700;
}
if (threadStackSize == 0) {
threadStackSize = 0x4000;
}
LOG_INFO(Lib_NpSignaling,
"memorySize={} threadPriority={} cpuAffinityMask={} threadStackSize={}", memorySize,
threadPriority, cpuAffinityMask, threadStackSize);
const s32 heap_rc = Helpers::InitSignalingHeap(memorySize);
if (heap_rc < 0) {
return heap_rc;
}
RegisterRuntimeHooks();
const s32 check_app_type_rc = Helpers::CheckAppType();
if (check_app_type_rc < 0) {
Helpers::ShutdownSignalingHeap();
return check_app_type_rc;
}
g_initialized = true;
const s32 rc = Helpers::StartMainRuntime(threadPriority, cpuAffinityMask, threadStackSize);
if (rc < 0) {
g_initialized = false;
Helpers::ShutdownSignalingHeap();
return rc;
}
const s32 echo_rc = Helpers::StartEchoRuntime(threadPriority, cpuAffinityMask);
if (echo_rc < 0) {
g_initialized = false;
Helpers::ShutdownRuntime();
Helpers::ShutdownSignalingHeap();
return echo_rc;
}
const s32 callout_rc = NpCommon::sceNpCalloutInitCtx(
&g_callout_ctx, "SceNpSignalingCallout", static_cast<u64>(threadStackSize), threadPriority,
static_cast<u64>(cpuAffinityMask));
if (callout_rc < 0) {
g_initialized = false;
Helpers::ShutdownRuntime();
Helpers::ShutdownSignalingHeap();
return callout_rc;
}
g_callout_ctx_active = true;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingTerminate() {
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
{
SignalingMutexGuard lock;
g_initialized = false;
}
Helpers::ShutdownRuntime();
if (g_callout_ctx_active) {
NpCommon::sceNpCalloutTermCtx(&g_callout_ctx);
g_callout_ctx_active = false;
}
Helpers::ShutdownSignalingHeap();
{
SignalingMutexGuard lock;
g_contexts.clear();
g_connections.clear();
g_npid_to_conn.clear();
g_peer_netinfo_results.clear();
}
DestroySignalingMutex();
LOG_INFO(Lib_NpSignaling, "cleared all state");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, void* callbackArg,
OrbisNpSignalingContextId* outContextId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!npId || !outContextId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId owner_npid{};
OrbisNpOnlineId owner_online_id{};
if (NormalizeNpId(npId, &owner_npid, &owner_online_id) != ORBIS_OK) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const bool transport_ready = Stubs::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
ctx_id = AllocateContextIdLocked();
if (ctx_id < 0) {
return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE;
}
NpSignalingContext& ctx = g_contexts[ctx_id];
ctx.callback = reinterpret_cast<OrbisNpSignalingHandler>(callback);
ctx.callback_arg = callbackArg;
ctx.active = true;
ctx.owner_npid = owner_npid;
ctx.owner_online_id = owner_online_id;
ctx.flags = 0x2;
ctx.compiled_sdk_version = CaptureCompiledSdkVersion();
ctx.activate_budget_us = kActivateBudgetMaxUs;
ctx.activate_last_update_us = NowUs();
ctx.bound_port = Stubs::ConfiguredPort();
*outContextId = ctx_id;
LOG_INFO(Lib_NpSignaling, "ctxId={} owner='{}' callback={} arg={} sdk={:#x} p2p_port={}",
ctx_id, OnlineIdToString(owner_online_id), fmt::ptr(callback),
fmt::ptr(callbackArg), ctx.compiled_sdk_version, ctx.bound_port);
}
if (transport_ready) {
SendStunPing(ctx_id);
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* callbackArg,
OrbisNpSignalingContextId* outContextId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (userId == -1 || !outContextId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId owner_npid{};
const s32 npid_rc = NpManager::sceNpGetNpId(userId, &owner_npid);
if (npid_rc < 0) {
return npid_rc;
}
// Account id isn't needed without matchmaking wired up; leave it 0 for now.
const OrbisNpAccountId account_id = 0;
const bool transport_ready = Stubs::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
ctx_id = AllocateContextIdLocked();
if (ctx_id < 0) {
return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE;
}
NpSignalingContext& ctx = g_contexts[ctx_id];
ctx.callback = reinterpret_cast<OrbisNpSignalingHandler>(callback);
ctx.callback_arg = callbackArg;
ctx.active = true;
ctx.owner_npid = owner_npid;
ctx.owner_online_id = owner_npid.handle;
ctx.flags = 0x4 | 0x2;
ctx.account_id = account_id;
ctx.platform_type = 0;
ctx.compiled_sdk_version = CaptureCompiledSdkVersion();
ctx.activate_budget_us = kActivateBudgetMaxUs;
ctx.activate_last_update_us = NowUs();
ctx.bound_port = Stubs::ConfiguredPort();
*outContextId = ctx_id;
LOG_INFO(Lib_NpSignaling, "ctxId={} userId={} owner='{}' accountId={:#x}", ctx_id, userId,
OnlineIdToString(ctx.owner_online_id), account_id);
}
if (transport_ready) {
SendStunPing(ctx_id);
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId) {
std::vector<s32> active_conns;
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
LOG_INFO(Lib_NpSignaling, "ctxId={}", ctxId);
for (const auto& [cid, ci] : g_connections) {
if (ci.ctx_id == ctxId && ci.status != ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) {
active_conns.push_back(cid);
}
}
}
for (const s32 cid : active_conns) {
CloseConnectionAndDispatchDead(cid, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER);
}
{
SignalingMutexGuard lock;
RemoveContextConnectionsLocked(ctxId);
for (auto it = g_peer_netinfo_results.begin(); it != g_peer_netinfo_results.end();) {
it = (it->second.ctx_id == ctxId) ? g_peer_netinfo_results.erase(it) : std::next(it);
}
g_contexts.erase(ctxId);
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxId,
const void* peerNpId,
OrbisNpSignalingConnectionId* outConnId) {
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} peerNpId={:p} connId={:p}", NowMs(), ctxId, peerNpId,
fmt::ptr(outConnId));
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!peerNpId || !outConnId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId peer_npid{};
OrbisNpOnlineId peer_online_id{};
if (NormalizeNpId(peerNpId, &peer_npid, &peer_online_id) != ORBIS_OK) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const std::string peer_online_id_str = OnlineIdToString(peer_online_id);
s32 cid = 0;
bool reused_established = false;
bool queue_activation = false;
bool start_handshake = true;
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (std::memcmp(&ctx_it->second.owner_npid, &peer_npid, sizeof(peer_npid)) == 0) {
return ORBIS_NP_SIGNALING_ERROR_OWN_NP_ID;
}
if (!ConsumeActivationBudgetGatedLocked(ctx_it->second)) {
return ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT;
}
const CtxNpIdKey lookup_key = MakeCtxNpIdKey(ctxId, peer_npid);
const auto existing_it = g_npid_to_conn.find(lookup_key);
if (existing_it != g_npid_to_conn.end()) {
const auto conn_it = g_connections.find(existing_it->second);
if (conn_it != g_connections.end() && conn_it->second.state == ConnState::Inactive) {
RemoveConnectionLocked(existing_it->second);
} else if (conn_it != g_connections.end()) {
cid = existing_it->second;
ConnectionInfo& ci = conn_it->second;
const bool was_locally_activated = ci.locally_activated;
ci.locally_activated = true;
if (ci.state == ConnState::Established) {
ClearLingerAndTimeoutLocked(cid);
reused_established = true;
start_handshake = false;
} else {
ClearLingerAndTimeoutLocked(cid);
start_handshake = true;
}
if (!was_locally_activated) {
queue_activation = true;
}
}
}
bool created_new = false;
if (cid == 0) {
cid = AllocateConnectionIdLocked();
if (cid < 0) {
return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY;
}
ConnectionInfo ci{};
ci.conn_id = cid;
ci.ctx_id = ctxId;
ci.state = ConnState::SendingOffer;
ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
ci.is_initiator = true;
ci.locally_activated = true;
ci.npid = peer_npid;
ci.online_id = peer_online_id;
g_connections[cid] = std::move(ci);
g_npid_to_conn[lookup_key] = cid;
created_new = true;
}
if (created_new) {
ArmConnectTimeoutLocked(cid);
queue_activation = true;
start_handshake = true;
}
if (queue_activation) {
QueueActivationLocked(cid, peer_online_id_str, start_handshake);
}
LOG_INFO(Lib_NpSignaling, "ctxId={} peer='{}' connId={} Status: {}", ctxId,
peer_online_id_str, cid,
created_new ? "queued, async"
: reused_established ? "queued local activation on established connection"
: queue_activation ? "queued local activation on existing connection"
: "reused transient");
}
*outConnId = cid;
if (queue_activation) {
g_dispatch_cv.notify_all();
return ORBIS_OK;
}
if (reused_established) {
EstablishConnection(cid, false);
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAddr || peerAddr->accountId == 0 || !outConnId || peerAddr->platformType == 0) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) != 0 && ctx_it->second.account_id == peerAddr->accountId &&
ctx_it->second.platform_type == peerAddr->platformType) {
return ORBIS_NP_SIGNALING_ERROR_OWN_PEER_ADDRESS;
}
if (!ConsumeActivationBudgetGatedLocked(ctx_it->second)) {
return ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT;
}
const s32 cid = AllocateConnectionIdLocked();
if (cid < 0) {
return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY;
}
ConnectionInfo ci{};
ci.conn_id = cid;
ci.ctx_id = ctxId;
ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
g_connections[cid] = std::move(ci);
*outConnId = cid;
LOG_INFO(Lib_NpSignaling, "ctxId={} accountId={:#x} platform={} connId={}", ctxId,
peerAddr->accountId, peerAddr->platformType, cid);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingDeactivateConnection(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} peer='{}' status={}", NowMs(), ctxId,
connId, OnlineIdToString(it->second.online_id), it->second.status);
}
DeactivateConnectionFaithful(connId);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingTerminateConnection(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
LOG_INFO(Lib_NpSignaling, "ctxId={} connId={}", ctxId, connId);
}
TerminateConnectionFaithful(connId);
{
SignalingMutexGuard lock;
RemoveConnectionLocked(connId);
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId,
u32* outStatus, u32* outPeerAddr,
u16* outPeerPort) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outStatus) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
return ORBIS_OK;
}
const s32 state = ConnectionStateFromStatus(it->second.status);
if (state == 10) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE;
if (outPeerAddr) {
*outPeerAddr = it->second.addr;
}
if (outPeerPort) {
*outPeerPort = it->second.port;
}
} else if (state != 0) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
} else {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
}
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} peer='{}' status={}", NowMs(), ctxId, connId,
OnlineIdToString(it->second.online_id), *outStatus);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfo(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outInfo) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const s32 rc = GetConnectionInfoInternal(ctxId, connId, infoCode, outInfo, nullptr);
LOG_INFO(Lib_NpSignaling, "ctxId={} connId={} infoCode={} rc={:#x}", ctxId, connId, infoCode,
static_cast<u32>(rc));
return rc;
}
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfoA(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outInfo) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const s32 rc = GetConnectionInfoInternal(ctxId, connId, infoCode, nullptr, outInfo);
LOG_INFO(Lib_NpSignaling, "ctxId={} connId={} infoCode={} rc={:#x}", ctxId, connId, infoCode,
static_cast<u32>(rc));
return rc;
}
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId ctxId,
const void* peerNpId,
OrbisNpSignalingConnectionId* outConnId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!peerNpId || !outConnId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId peer_npid{};
OrbisNpOnlineId peer_online_id{};
if (NormalizeNpId(peerNpId, &peer_npid, &peer_online_id) != ORBIS_OK) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_npid_to_conn.find(MakeCtxNpIdKey(ctxId, peer_npid));
if (it == g_npid_to_conn.end()) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
const auto conn_it = g_connections.find(it->second);
if (conn_it == g_connections.end()) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
*outConnId = it->second;
LOG_INFO(Lib_NpSignaling, "ctxId={} npid='{}' connId={}", ctxId,
OnlineIdToString(peer_online_id), it->second);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI
sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32 peerAddr,
u16 peerPort, OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outConnId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
for (const auto& [id, ci] : g_connections) {
if (ci.ctx_id == ctxId && ci.addr == peerAddr && ci.port == peerPort &&
ConnectionStateFromStatus(ci.status) == 10) {
*outConnId = id;
LOG_INFO(Lib_NpSignaling, "ctxId={} addr={:#x} port={} connId={}", ctxId, peerAddr,
sceNetNtohs(peerPort), id);
return ORBIS_OK;
}
}
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAddr || !outConnId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) == 0) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpSignaling, "ctxId={} accountId={:#x}", ctxId, peerAddr->accountId);
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32 optionValue) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
const auto it = g_contexts.find(ctxId);
if (it == g_contexts.end() || !it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (optionValue == 0) {
it->second.flags &= ~0x2u;
} else if (optionValue == 1) {
it->second.flags |= 0x2u;
} else {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32* outOptionValue) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outOptionValue) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto it = g_contexts.find(ctxId);
if (it == g_contexts.end() || !it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
*outOptionValue = (it->second.flags & 0x2u) != 0 ? 1 : 0;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingNetInfo* info) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!info) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (info->size != sizeof(OrbisNpSignalingNetInfo)) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
info->localAddr = ParseIpv4Nbo(netinfo->GetIp());
NetCtl::OrbisNetCtlNatInfo nat_info{};
nat_info.size = sizeof(nat_info);
info->mappedAddr = 0;
info->natStatus = 0;
if (NetCtl::sceNetCtlGetNatInfo(&nat_info) >= 0) {
info->mappedAddr = nat_info.mapped_addr;
info->natStatus = nat_info.nat_type;
}
if (info->mappedAddr == 0) {
const u32 external = netinfo->GetExternalIp();
info->mappedAddr = external != 0 ? external : info->localAddr;
}
info->_pad_14 = 0;
LOG_INFO(Lib_NpSignaling, "localAddr={:#x} mappedAddr={:#x} natStatus={}", info->localAddr,
info->mappedAddr, info->natStatus);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfo(OrbisNpSignalingContextId ctxId, const void* peerNpId,
OrbisNpSignalingRequestId* outReqId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!peerNpId || !outReqId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId peer_npid{};
if (NormalizeNpId(peerNpId, &peer_npid, nullptr) != ORBIS_OK) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
s32 req_id = 0;
{
SignalingMutexGuard lock;
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
req_id = StagePeerNetInfoResultLocked(ctxId);
if (req_id == 0) {
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_OUT_OF_MEMORY;
}
}
*outReqId = static_cast<u32>(req_id);
LOG_INFO(Lib_NpSignaling, "ctxId={} reqId={:#x}", ctxId, *outReqId);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId,
const void* peerAccountPayload,
OrbisNpSignalingRequestId* outReqId) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAccountPayload || !outReqId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) == 0) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const s32 req_id = StagePeerNetInfoResultLocked(ctxId);
if (req_id == 0) {
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_OUT_OF_MEMORY;
}
*outReqId = static_cast<u32>(req_id);
LOG_INFO(Lib_NpSignaling, "ctxId={} reqId={:#x}", ctxId, *outReqId);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingCancelPeerNetInfo(OrbisNpSignalingContextId ctxId, s32 reqOrConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (!DropPeerNetInfoResultLocked(ctxId, reqOrConnId)) {
return ORBIS_NP_SIGNALING_ERROR_REQ_NOT_FOUND;
}
LOG_INFO(Lib_NpSignaling, "ctxId={} reqOrConnId={:#x}", ctxId, reqOrConnId);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId,
OrbisNpSignalingNetInfo* peerNetInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerNetInfo) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (peerNetInfo->size != sizeof(OrbisNpSignalingNetInfo)) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
PeerNetInfoResult result{};
if (!TakePeerNetInfoResultLocked(ctxId, connId, &result)) {
return ORBIS_NP_SIGNALING_ERROR_RESULT_NOT_FOUND;
}
peerNetInfo->localAddr = result.local_ipv4;
peerNetInfo->mappedAddr = result.external_ipv4;
peerNetInfo->natStatus = static_cast<s32>(result.nat_route_kind);
peerNetInfo->_pad_14 = 0;
LOG_INFO(Lib_NpSignaling, "ctxId={} connId={:#x} ext={:#x}", ctxId, connId,
result.external_ipv4);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info) {
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!info) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
info->currentInUse = 0;
info->peakInUse = 0;
info->maxSystemSize = 0x20000;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI
sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stats) {
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!stats) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
SignalingMutexGuard lock;
SnapshotConnectionStatisticsLocked(&stats->peakConnectionCount, &stats->activeConnectionCount,
&stats->transientConnectionCount,
&stats->establishedConnectionCount);
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("0UvTFeomAUM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnection);
LIB_FUNCTION("ZPLavCKqAB0", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnectionA);
LIB_FUNCTION("X1G4kkN2R-8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCancelPeerNetInfo);
LIB_FUNCTION("5yYjEdd4t8Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContext);
LIB_FUNCTION("dDLNFdY8dws", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContextA);
LIB_FUNCTION("6UEembipgrM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeactivateConnection);
LIB_FUNCTION("hx+LIg-1koI", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeleteContext);
LIB_FUNCTION("GQ0hqmzj0F4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromNpId);
LIB_FUNCTION("CkPxQjSm018", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddress);
LIB_FUNCTION("B7cT9aVby7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddressA);
LIB_FUNCTION("AN3h0EBSX7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfo);
LIB_FUNCTION("rcylknsUDwg", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfoA);
LIB_FUNCTION("C6ZNCDTj00Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatistics);
LIB_FUNCTION("bD-JizUb3JM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatus);
LIB_FUNCTION("npU5V56id34", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetContextOption);
LIB_FUNCTION("U8AQMlOFBc8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetLocalNetInfo);
LIB_FUNCTION("tOpqyDyMje4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetMemoryInfo);
LIB_FUNCTION("zFgFHId7vAE", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfo);
LIB_FUNCTION("Shr7bZq8QHY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoA);
LIB_FUNCTION("2HajCEGgG4s", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoResult);
LIB_FUNCTION("3KOuC4RmZZU", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingInitialize);
LIB_FUNCTION("IHRDvZodPYY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingSetContextOption);
LIB_FUNCTION("NPhw0UXaNrk", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminate);
LIB_FUNCTION("b4qaXPzMJxo", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminateConnection);
}
} // namespace Libraries::Np::NpSignaling
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "common/types.h"
#include "core/libraries/np/np_types2.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpSignaling {
s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s32 cpuAffinityMask,
s64 threadStackSize);
s32 PS4_SYSV_ABI sceNpSignalingTerminate();
s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, void* callbackArg,
OrbisNpSignalingContextId* outContextId);
s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void* callbackArg,
OrbisNpSignalingContextId* outContextId);
s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId);
s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxId,
const void* peerNpId,
OrbisNpSignalingConnectionId* outConnId);
s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId);
s32 PS4_SYSV_ABI sceNpSignalingDeactivateConnection(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId);
s32 PS4_SYSV_ABI sceNpSignalingTerminateConnection(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId,
u32* outStatus, u32* outPeerAddr,
u16* outPeerPort);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfo(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfoA(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId ctxId,
const void* peerNpId,
OrbisNpSignalingConnectionId* outConnId);
s32 PS4_SYSV_ABI
sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32 peerAddr,
u16 peerPort, OrbisNpSignalingConnectionId* outConnId);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId);
s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32 optionValue);
s32 PS4_SYSV_ABI sceNpSignalingGetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32* outOptionValue);
s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingNetInfo* info);
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfo(OrbisNpSignalingContextId ctxId, const void* peerNpId,
OrbisNpSignalingRequestId* outReqId);
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId,
const void* peerAccountPayload,
OrbisNpSignalingRequestId* outReqId);
s32 PS4_SYSV_ABI sceNpSignalingCancelPeerNetInfo(OrbisNpSignalingContextId ctxId, s32 reqOrConnId);
s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingConnectionId connId,
OrbisNpSignalingNetInfo* peerNetInfo);
s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info);
s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stats);
s32 GetActiveConnectionIdForPeer(std::string_view online_id);
s32 GetConnectionStatusForPeer(std::string_view online_id, s32* out_conn_id);
bool GetPeerAddress(std::string_view online_id, u32* out_addr, u16* out_port);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpSignaling
@@ -0,0 +1,523 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <cstring>
#include "common/alignment.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/net_util.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
namespace Libraries::Np::NpSignaling::Helpers {
namespace {
constexpr s32 InferredNpAppType = 0;
bool g_signaling_heap_initialized = false;
void* g_signaling_heap_base = nullptr;
s64 g_signaling_heap_size = 0;
SignalingRuntimeHooks g_runtime_hooks{};
bool g_runtime_hooks_registered = false;
u64 g_echo_probe_word2 = 0;
u64 g_echo_probe_word3 = 0;
u64 g_echo_thread_handle = static_cast<u64>(-1);
s32 g_echo_thread_status = -1;
s32 GetInferredAppType(s32* app_type) {
if (app_type == nullptr) {
return ORBIS_NP_INT_ERROR_INVALID_ARGUMENT;
}
*app_type = InferredNpAppType;
return ORBIS_OK;
}
s16 GetAppTypeStateGate() {
return 0;
}
void SetAppTypeMarker(u16 marker) {
LOG_DEBUG(Lib_NpSignaling, "marker={:#x}", marker);
}
} // namespace
void SetRuntimeHooks(const SignalingRuntimeHooks& hooks) {
g_runtime_hooks = hooks;
g_runtime_hooks_registered = true;
}
s32 CheckInitializeAppType(u32* is_app_type_4) {
s32 app_type = -1;
const s32 rc = GetInferredAppType(&app_type);
if (rc < 0) {
return rc;
}
if (is_app_type_4 != nullptr) {
*is_app_type_4 = app_type == 4 ? 1u : 0u;
}
return ORBIS_OK;
}
s32 InitSignalingHeap(s64 pool_size) {
LOG_DEBUG(Lib_NpSignaling, "pool_size={}", pool_size);
if (pool_size == 0 || g_signaling_heap_initialized) {
return ORBIS_NP_SIGNALING_INTERNAL_ERROR_ALLOCATOR;
}
const u64 aligned_size = Common::AlignUp(static_cast<u64>(pool_size), 0x4000);
void* heap_base = nullptr;
const s32 rc = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&heap_base, aligned_size, 3,
0, "SceNpSignaling");
if (rc < 0) {
return rc;
}
g_signaling_heap_initialized = true;
g_signaling_heap_base = heap_base;
g_signaling_heap_size = static_cast<s64>(aligned_size);
return ORBIS_OK;
}
void ShutdownSignalingHeap() {
if (!g_signaling_heap_initialized) {
return;
}
g_signaling_heap_initialized = false;
if (g_signaling_heap_base != nullptr) {
Libraries::Kernel::sceKernelMunmap(g_signaling_heap_base,
static_cast<u64>(g_signaling_heap_size));
g_signaling_heap_base = nullptr;
g_signaling_heap_size = 0;
}
}
s32 CheckAppType() {
s32 app_type = -1;
const s32 rc = GetInferredAppType(&app_type);
if (rc < 0) {
return rc;
}
if (app_type == 5) {
const s16 gate = GetAppTypeStateGate();
if (gate != 0) {
return ORBIS_OK;
}
}
SetAppTypeMarker(0x245a);
return ORBIS_OK;
}
s32 StartMainRuntime(s32 thread_priority, s32 cpu_affinity_mask, s64 thread_stack_size) {
LOG_DEBUG(Lib_NpSignaling,
"thread_priority={} cpu_affinity_mask={} "
"thread_stack_size={}",
thread_priority, cpu_affinity_mask, thread_stack_size);
if (!g_runtime_hooks_registered) {
return ORBIS_NP_SIGNALING_INTERNAL_ERROR_NOT_INITIALIZED;
}
const s32 priority = thread_priority;
const u64 affinity = static_cast<u64>(static_cast<u32>(cpu_affinity_mask));
const u64 stack_size = static_cast<u64>(thread_stack_size);
if (g_runtime_hooks.start_dispatch != nullptr) {
g_runtime_hooks.start_dispatch(priority, affinity, stack_size);
}
if (g_runtime_hooks.start_receive != nullptr) {
g_runtime_hooks.start_receive(priority, affinity, stack_size);
}
if (g_runtime_hooks.start_ping != nullptr) {
g_runtime_hooks.start_ping(priority, affinity, stack_size);
}
return ORBIS_OK;
}
s32 StartEchoRuntime(s32 thread_priority, s32 cpu_affinity_mask) {
LOG_DEBUG(Lib_NpSignaling, "thread_priority={} cpu_affinity_mask={}", thread_priority,
cpu_affinity_mask);
auto sock = Libraries::Net::sceNetSocket("SceNpSignalingIoctl", 2, 6, 0);
if (sock < 0) {
return sock;
}
const s32 ioctl_rc = Libraries::Net::sceNetIoctl();
Libraries::Net::sceNetSocketClose(sock);
g_echo_probe_word2 = 0;
g_echo_probe_word3 = 0;
g_echo_thread_handle = static_cast<u64>(-1);
g_echo_thread_status = -1;
return ioctl_rc;
}
void ShutdownRuntime() {
if (!g_runtime_hooks_registered) {
g_echo_probe_word2 = 0;
g_echo_probe_word3 = 0;
g_echo_thread_handle = static_cast<u64>(-1);
g_echo_thread_status = -1;
return;
}
if (g_runtime_hooks.stop_ping != nullptr) {
g_runtime_hooks.stop_ping();
}
if (g_runtime_hooks.stop_receive != nullptr) {
g_runtime_hooks.stop_receive();
}
if (g_runtime_hooks.stop_dispatch != nullptr) {
g_runtime_hooks.stop_dispatch();
}
g_echo_probe_word2 = 0;
g_echo_probe_word3 = 0;
g_echo_thread_handle = static_cast<u64>(-1);
g_echo_thread_status = -1;
}
} // namespace Libraries::Np::NpSignaling::Helpers
namespace Libraries::Np::NpSignaling {
using Libraries::Net::sceNetNtohs;
static Kernel::PthreadT g_dispatch_thread{};
static Kernel::PthreadT g_receive_thread{};
static bool g_receive_stop = false;
static Kernel::PthreadT g_ping_thread{};
static bool g_ping_stop = false;
static void HandleStunEcho(s32 ctx_id, const StunEcho& echo) {
SignalingMutexGuard lock;
const auto it = g_contexts.find(ctx_id);
if (it == g_contexts.end() || !it->second.active) {
return;
}
NpSignalingContext& ctx = it->second;
ctx.ext_addr.store(echo.ext_ip);
ctx.ext_port.store(echo.ext_port);
LOG_DEBUG(Lib_NpSignaling, "STUN echo: ctxId={} ext_addr={:#x} ext_port={}", ctx_id,
echo.ext_ip, sceNetNtohs(echo.ext_port));
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
netinfo->SetExternalIp(echo.ext_ip);
ctx.stun_cv.notify_all();
}
static bool HasSignalingMagic(const u8* buf, size_t nbytes) {
return nbytes >= 5 && std::memcmp(buf, kSignalingMagic, sizeof(kSignalingMagic)) == 0;
}
static void DrainControlPackets() {
static constexpr u32 kBufSize = 256;
u8 buf[kBufSize];
for (;;) {
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Stubs::ControlRecvFrom(buf, kBufSize, &from_addr, &from_port);
if (rc <= 0) {
break;
}
const auto nbytes = static_cast<size_t>(rc);
if (nbytes == sizeof(SignalingControl) && HasSignalingMagic(buf, nbytes) &&
buf[4] == static_cast<u8>(SignalingPacketType::Control)) {
SignalingControl ctrl{};
std::memcpy(&ctrl, buf, sizeof(ctrl));
HandleControlPacket(from_addr, from_port, ctrl);
} else {
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: bad control packet (size={})", nbytes);
}
}
}
static void ReceiveThreadMain() {
static constexpr u32 kBufSize = 256;
u8 buf[kBufSize];
while (!g_receive_stop) {
DrainControlPackets();
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Stubs::SignalingRecvFrom(buf, kBufSize, &from_addr, &from_port);
if (rc <= 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
const auto nbytes = static_cast<size_t>(rc);
if (nbytes != sizeof(StunEcho)) {
LOG_DEBUG(
Lib_NpSignaling,
"ReceiveThread DATA from {:#x}:{} size={} b0-4={:02x},{:02x},{:02x},{:02x},{:02x}",
from_addr, sceNetNtohs(from_port), nbytes, buf[0], buf[1], buf[2], buf[3],
nbytes >= 5 ? buf[4] : 0);
}
if (nbytes == sizeof(StunEcho)) {
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
for (const auto& [cid, ctx] : g_contexts) {
if (ctx.active) {
ctx_id = cid;
break;
}
}
}
if (ctx_id == 0) {
continue;
}
StunEcho echo{};
std::memcpy(&echo, buf, sizeof(echo));
HandleStunEcho(ctx_id, echo);
continue;
}
if (!HasSignalingMagic(buf, nbytes)) {
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: dropping non-SHAD packet (size={})",
nbytes);
continue;
}
const u8 type = buf[4];
if (nbytes == sizeof(SignalingEchoPing) &&
type == static_cast<u8>(SignalingPacketType::EchoPing)) {
SignalingEchoPing ping{};
std::memcpy(&ping, buf, sizeof(ping));
SignalingEchoPong pong{};
pong.conn_id = ping.conn_id;
pong.orig_ts_us = ping.send_ts_us;
Stubs::SignalingSendTo(&pong, sizeof(pong), from_addr, from_port);
continue;
}
if (nbytes == sizeof(SignalingEchoPong) &&
type == static_cast<u8>(SignalingPacketType::EchoPong)) {
SignalingEchoPong pong{};
std::memcpy(&pong, buf, sizeof(pong));
const s64 now = NowUs();
s32 rtt_us = static_cast<s32>(now - static_cast<s64>(pong.orig_ts_us));
if (rtt_us < 0) {
rtt_us = 0;
}
{
SignalingMutexGuard lock;
RecordRttSampleLocked(static_cast<s32>(pong.conn_id), rtt_us);
}
continue;
}
if (nbytes == sizeof(SignalingHandshake) &&
type == static_cast<u8>(SignalingPacketType::Handshake)) {
SignalingHandshake hs{};
std::memcpy(&hs, buf, sizeof(hs));
HandleHandshakePacket(from_addr, from_port, hs);
continue;
}
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: unexpected SHAD packet type={} size={}", type,
nbytes);
}
}
static PS4_SYSV_ABI void* ReceiveThreadFunc(void*) {
ReceiveThreadMain();
return nullptr;
}
static void StartReceiveThread(s32 priority, u64 affinity_mask, u64 stack_size) {
g_receive_stop = false;
if (!g_receive_thread) {
NpCommon::sceNpCreateThread(&g_receive_thread, ReceiveThreadFunc, nullptr, priority,
stack_size, affinity_mask, "SceNpSignalingRecv");
}
}
static void StopReceiveThread() {
{
SignalingMutexGuard lock;
g_receive_stop = true;
}
if (g_receive_thread) {
NpCommon::sceNpJoinThread(g_receive_thread, nullptr);
g_receive_thread = {};
}
}
static void PingThreadMain() {
while (!g_ping_stop) {
if (!Stubs::EnsureTransport()) {
std::this_thread::sleep_for(std::chrono::milliseconds(kSigRetryMs));
continue;
}
struct CtxSnapshot {
s32 ctx_id;
OrbisNpOnlineId online_id{};
bool resolved;
};
std::vector<CtxSnapshot> contexts;
{
SignalingMutexGuard lock;
for (const auto& [ctx_id, ctx] : g_contexts) {
if (ctx.active) {
contexts.push_back({ctx_id, ctx.owner_online_id, ctx.ext_addr.load() != 0});
}
}
}
if (Stubs::Matching2Enabled()) {
const u32 server_addr = Stubs::MmServerAddr();
const u16 server_port = Stubs::MmServerUdpPort();
for (const auto& cs : contexts) {
if (server_addr == 0 || server_port == 0) {
break;
}
StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, cs.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = Stubs::AdvertisedAddr();
Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_port);
}
}
SendEchoPings();
const bool any_unresolved = std::any_of(contexts.begin(), contexts.end(),
[](const auto& cs) { return !cs.resolved; });
const u32 sleep_ms = any_unresolved ? kSigRetryMs : kSigPingMs;
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
}
}
static PS4_SYSV_ABI void* PingThreadFunc(void*) {
PingThreadMain();
return nullptr;
}
static void StartPingThread(s32 priority, u64 affinity_mask, u64 stack_size) {
g_ping_stop = false;
if (!g_ping_thread) {
NpCommon::sceNpCreateThread(&g_ping_thread, PingThreadFunc, nullptr, priority, stack_size,
affinity_mask, "SceNpSignalingPing");
}
}
static void StopPingThread() {
g_ping_stop = true;
if (g_ping_thread) {
NpCommon::sceNpJoinThread(g_ping_thread, nullptr);
g_ping_thread = {};
}
}
static void DispatchThreadMain() {
for (;;) {
ProcessPendingActivations();
QueuedDispatch dispatch;
{
std::unique_lock<std::mutex> lock(g_dispatch_mutex);
for (;;) {
if (g_dispatch_stop) {
return;
}
if (g_dispatch_queue.empty()) {
g_dispatch_cv.wait_for(lock, std::chrono::seconds(1), [] {
return g_dispatch_stop || !g_dispatch_queue.empty();
});
break;
}
const auto it = g_dispatch_queue.begin();
const auto now = std::chrono::steady_clock::now();
if (it->first > now) {
g_dispatch_cv.wait_until(lock, it->first);
break;
}
dispatch = std::move(it->second);
g_dispatch_queue.erase(it);
break;
}
}
if (dispatch.callback) {
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} event={}({}) delay={}ms", NowMs(),
dispatch.ctx_id, dispatch.conn_id, dispatch.event_type,
SignalingEventName(dispatch.event_type), dispatch.delay_ms);
LOG_DEBUG(Lib_NpSignaling,
"INVOKE signaling_cb={} ctxId={} connId={} event={}({}) errorCode={} arg={}",
fmt::ptr(reinterpret_cast<void*>(dispatch.callback)), dispatch.ctx_id,
dispatch.conn_id, dispatch.event_type,
SignalingEventName(dispatch.event_type), dispatch.error_code,
fmt::ptr(dispatch.callback_arg));
dispatch.callback(static_cast<u32>(dispatch.ctx_id), static_cast<u32>(dispatch.conn_id),
dispatch.event_type, dispatch.error_code, dispatch.callback_arg);
}
}
}
static PS4_SYSV_ABI void* DispatchThreadFunc(void*) {
DispatchThreadMain();
return nullptr;
}
static void StartDispatchThread(s32 priority, u64 affinity_mask, u64 stack_size) {
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
g_dispatch_stop = false;
if (!g_dispatch_thread) {
NpCommon::sceNpCreateThread(&g_dispatch_thread, DispatchThreadFunc, nullptr, priority,
stack_size, affinity_mask, "SceNpSignalingMain");
}
}
static void StopDispatchThread() {
{
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
g_dispatch_stop = true;
g_dispatch_queue.clear();
}
g_dispatch_cv.notify_all();
if (g_dispatch_thread) {
NpCommon::sceNpJoinThread(g_dispatch_thread, nullptr);
g_dispatch_thread = {};
}
}
void RegisterRuntimeHooks() {
Helpers::SetRuntimeHooks({
.start_dispatch = StartDispatchThread,
.start_receive = StartReceiveThread,
.start_ping = StartPingThread,
.stop_ping = StopPingThread,
.stop_receive = StopReceiveThread,
.stop_dispatch = StopDispatchThread,
});
}
} // namespace Libraries::Np::NpSignaling
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Libraries::Np::NpSignaling::Helpers {
struct SignalingRuntimeHooks {
void (*start_dispatch)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*start_receive)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*start_ping)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*stop_ping)();
void (*stop_receive)();
void (*stop_dispatch)();
};
void SetRuntimeHooks(const SignalingRuntimeHooks& hooks);
s32 CheckInitializeAppType(u32* is_app_type_4);
s32 InitSignalingHeap(s64 pool_size);
void ShutdownSignalingHeap();
s32 CheckAppType();
s32 StartMainRuntime(s32 thread_priority, s32 cpu_affinity_mask, s64 thread_stack_size);
s32 StartEchoRuntime(s32 thread_priority, s32 cpu_affinity_mask);
void ShutdownRuntime();
} // namespace Libraries::Np::NpSignaling::Helpers
namespace Libraries::Np::NpSignaling {
void RegisterRuntimeHooks();
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,357 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <map>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_types.h"
namespace Libraries::Np::NpSignaling {
extern NpCommon::OrbisNpCalloutContext g_callout_ctx;
extern bool g_callout_ctx_active;
constexpr s64 kActivateCooldownUs = 6'000'000;
constexpr s64 kActivateBudgetMaxUs = 600'000'000;
constexpr u32 kSigRetryMs = 500;
constexpr u32 kSigPingMs = 5'000;
#pragma pack(push, 1)
struct StunPing {
u8 cmd = 0x01;
u8 online_id[16]{};
u32 local_ip = 0;
};
static_assert(sizeof(StunPing) == 21, "StunPing must be exactly 21 bytes");
#pragma pack(pop)
#pragma pack(push, 1)
struct StunEcho {
u32 ext_ip = 0;
u16 ext_port = 0;
};
static_assert(sizeof(StunEcho) == 6, "StunEcho must be exactly 6 bytes");
#pragma pack(pop)
inline constexpr u8 kSignalingMagic[4] = {'S', 'H', 'A', 'D'};
enum class SignalingPacketType : u8 {
EchoPing = 0x05,
EchoPong = 0x06,
Handshake = 0x07,
Control = 0x10,
};
#pragma pack(push, 1)
struct SignalingEchoPing {
u8 magic[4] = {kSignalingMagic[0], kSignalingMagic[1], kSignalingMagic[2], kSignalingMagic[3]};
u8 type = static_cast<u8>(SignalingPacketType::EchoPing);
u32 conn_id = 0;
u64 send_ts_us = 0;
};
static_assert(sizeof(SignalingEchoPing) == 17);
struct SignalingEchoPong {
u8 magic[4] = {kSignalingMagic[0], kSignalingMagic[1], kSignalingMagic[2], kSignalingMagic[3]};
u8 type = static_cast<u8>(SignalingPacketType::EchoPong);
u32 conn_id = 0;
u64 orig_ts_us = 0;
};
static_assert(sizeof(SignalingEchoPong) == 17);
#pragma pack(pop)
#pragma pack(push, 1)
struct SignalingHandshake {
u8 magic[4] = {kSignalingMagic[0], kSignalingMagic[1], kSignalingMagic[2], kSignalingMagic[3]};
u8 type = static_cast<u8>(SignalingPacketType::Handshake);
u8 kind = 0;
u8 online_id_from[16]{};
u8 online_id_to[16]{};
u32 from_conn_id = 0;
u32 to_conn_id = 0;
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reserved = 0;
u64 nonce = 0;
};
static_assert(sizeof(SignalingHandshake) == 0x3e);
#pragma pack(pop)
enum class HandshakeKind : u8 {
Offer = 1,
Accept = 2,
Check = 3,
CheckAck = 4,
};
#pragma pack(push, 1)
struct SignalingControl {
u8 magic[4] = {kSignalingMagic[0], kSignalingMagic[1], kSignalingMagic[2], kSignalingMagic[3]};
u8 type = static_cast<u8>(SignalingPacketType::Control);
u8 kind = 0;
u8 online_id_from[16]{};
u8 online_id_to[16]{};
u32 from_conn_id = 0;
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reason = 0;
};
static_assert(sizeof(SignalingControl) == 0x32);
#pragma pack(pop)
enum class ControlKind : u8 {
ActivationRequest = 1,
ActivationAck = 2,
Close = 3,
Established = 4,
};
enum class ControlReason : u16 {
Terminate = 0,
Deactivate = 1,
};
enum class ConnState : s32 {
Inactive = 0,
SendingOffer = 5,
WaitOffer = 6,
ConnCheck = 7,
SendingAccept = 8,
WaitAccept = 9,
Established = 10,
};
struct ConnectionInfo {
OrbisNpSignalingConnectionId conn_id = 0;
OrbisNpSignalingContextId ctx_id = 0;
u32 addr = 0;
u16 port = 0;
u32 local_addr = 0;
u16 local_port = 0;
s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
OrbisNpId npid{};
OrbisNpOnlineId online_id{};
s32 activation_mode = 0;
static constexpr u32 kProbeSampleCount = 6;
s32 probe_rtt_samples[kProbeSampleCount] = {-1, -1, -1, -1, -1, -1};
u32 probe_sample_write_index = 0;
s64 last_echo_ping_us = 0;
s64 last_peer_rx_us = 0;
ConnState state = ConnState::Inactive;
bool established_fired = false;
bool established_event_fired = false;
bool peer_activated = false;
bool locally_activated = false;
bool peer_activated_fired = false;
bool peer_established = false;
bool mutual_fired = false;
bool dead_fired = false;
bool is_initiator = false;
s64 last_handshake_send_us = 0;
NpCommon::OrbisNpCalloutEntry timeout_callout{};
bool timeout_callout_armed = false;
NpCommon::OrbisNpCalloutEntry step_callout{};
bool step_callout_armed = false;
bool deactivate_lingering = false;
u32 echo_derived_value = 0;
};
struct NpSignalingContext {
OrbisNpSignalingHandler callback{nullptr};
void* callback_arg{nullptr};
bool active{false};
u32 flags{0};
u32 compiled_sdk_version{0};
u64 account_id{0};
u32 platform_type{0};
OrbisNpId owner_npid{};
OrbisNpOnlineId owner_online_id{};
u16 bound_port{0};
std::atomic<u32> ext_addr{0};
std::atomic<u16> ext_port{0};
std::mutex stun_mutex{};
std::condition_variable stun_cv{};
s64 activate_budget_us{0};
s64 activate_last_update_us{0};
};
struct CtxNpIdKey {
OrbisNpSignalingContextId ctx_id = 0;
OrbisNpId npid{};
};
struct CtxNpIdKeyHash {
size_t operator()(const CtxNpIdKey& key) const noexcept {
size_t hash = static_cast<size_t>(key.ctx_id);
const auto* bytes = reinterpret_cast<const u8*>(&key.npid);
for (size_t i = 0; i < sizeof(key.npid); ++i) {
hash ^= static_cast<size_t>(bytes[i]) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
return hash;
}
};
inline bool operator==(const CtxNpIdKey& lhs, const CtxNpIdKey& rhs) {
return lhs.ctx_id == rhs.ctx_id && std::memcmp(&lhs.npid, &rhs.npid, sizeof(lhs.npid)) == 0;
}
struct QueuedDispatch {
std::chrono::steady_clock::time_point fire_at{};
u32 delay_ms = 0;
OrbisNpSignalingContextId ctx_id = 0;
OrbisNpSignalingConnectionId conn_id = 0;
u32 event_type = 0;
u32 error_code = 0;
OrbisNpSignalingHandler callback = nullptr;
void* callback_arg = nullptr;
};
constexpr u32 kPeerNetInfoRequestIdPrefix = 0x21000000;
struct PeerNetInfoResult {
OrbisNpSignalingContextId ctx_id = 0;
OrbisNpSignalingConnectionId conn_id = 0;
u32 local_ipv4 = 0;
u32 external_ipv4 = 0;
u32 nat_route_kind = 0;
};
extern std::unordered_map<s32, NpSignalingContext> g_contexts;
extern std::unordered_map<s32, ConnectionInfo> g_connections;
extern std::unordered_map<CtxNpIdKey, s32, CtxNpIdKeyHash> g_npid_to_conn;
extern std::unordered_map<s32, PeerNetInfoResult> g_peer_netinfo_results;
struct PendingActivation {
OrbisNpSignalingConnectionId conn_id = 0;
std::string peer_online_id;
bool start_handshake = true;
};
extern std::vector<PendingActivation> g_pending_activations;
extern u32 g_peer_netinfo_next_id;
extern u32 g_peak_connection_count;
extern u32 g_last_assigned_context_id;
extern u32 g_connection_id_seed;
extern bool g_initialized;
extern Libraries::Kernel::PthreadMutexT g_mutex_storage;
struct SignalingMutexGuard {
SignalingMutexGuard();
~SignalingMutexGuard();
SignalingMutexGuard(const SignalingMutexGuard&) = delete;
SignalingMutexGuard& operator=(const SignalingMutexGuard&) = delete;
};
void InitSignalingMutex();
void DestroySignalingMutex();
extern std::multimap<std::chrono::steady_clock::time_point, QueuedDispatch> g_dispatch_queue;
extern std::mutex g_dispatch_mutex;
extern std::condition_variable g_dispatch_cv;
extern bool g_dispatch_stop;
long long NowMs();
s64 NowUs();
std::string OnlineIdToString(const OrbisNpOnlineId& online_id_in);
std::string OnlineIdFromNpId(const OrbisNpId& np_id);
OrbisNpId NpIdFromOnlineId(const OrbisNpOnlineId& online_id);
bool OnlineIdEqualsString(const OrbisNpOnlineId& online_id, std::string_view value);
bool IsValidNpId(const OrbisNpId& np_id);
s32 NormalizeNpId(const void* np_id, OrbisNpId* out_npid, OrbisNpOnlineId* out_online_id = nullptr);
CtxNpIdKey MakeCtxNpIdKey(OrbisNpSignalingContextId ctx_id, const OrbisNpId& npid);
bool IsContextValidLocked(OrbisNpSignalingContextId ctx_id);
OrbisNpSignalingContextId AllocateContextIdLocked();
OrbisNpSignalingConnectionId AllocateConnectionIdLocked();
void RemoveConnectionLocked(OrbisNpSignalingConnectionId conn_id);
void RemoveContextConnectionsLocked(OrbisNpSignalingContextId ctx_id);
s32 ConnectionStateFromStatus(s32 status);
u32 CaptureCompiledSdkVersion();
u32 ParseIpv4Nbo(const std::string& dotted);
s32 GetConnectionInfoInternal(OrbisNpSignalingContextId ctx_id,
OrbisNpSignalingConnectionId conn_id, s32 info_code, void* out_a,
void* out_b);
bool ConsumeActivationBudgetGatedLocked(NpSignalingContext& ctx);
void SnapshotConnectionStatisticsLocked(u32* out_peak, u32* out_active, u32* out_transient,
u32* out_established);
OrbisNpSignalingRequestId StagePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id);
bool TakePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_conn_id,
PeerNetInfoResult* out);
bool DropPeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_conn_id);
void RecordRttSampleLocked(OrbisNpSignalingConnectionId conn_id, s32 rtt_us);
void SendEchoPings();
void DispatchConnectionEvent(OrbisNpSignalingConnectionId conn_id, s32 event_type, s32 event_data);
void DispatchPeerActivatedEvent(OrbisNpSignalingConnectionId conn_id);
void CloseConnectionAndDispatchDead(OrbisNpSignalingConnectionId conn_id, s32 error_code);
void EstablishConnection(OrbisNpSignalingConnectionId conn_id, bool peer_activated_hint);
void DeactivateConnectionFaithful(OrbisNpSignalingConnectionId conn_id);
void TerminateConnectionFaithful(OrbisNpSignalingConnectionId conn_id);
void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id);
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id,
bool start_handshake = true);
void ProcessPendingActivations();
void HandleHandshakePacket(u32 from_addr, u16 from_port, const SignalingHandshake& pkt);
void ArmConnectTimeoutLocked(OrbisNpSignalingConnectionId conn_id);
void ClearLingerAndTimeoutLocked(OrbisNpSignalingConnectionId conn_id);
void SendActivationRequestLocked(const ConnectionInfo& ci);
void SendControlCloseLocked(const ConnectionInfo& ci, ControlReason reason);
void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& pkt);
const char* SignalingEventName(u32 event_type);
bool ConsumeActivationBudgetLocked(NpSignalingContext& ctx);
void SendStunPing(OrbisNpSignalingContextId ctx_id);
void ClearActiveDeadlines();
void ClearConnectionDeadlineForPeer(std::string_view npid);
void SetRoomLeft();
} // namespace Libraries::Np::NpSignaling
@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
namespace Libraries::Np::NpSignaling::Stubs {
namespace {
TransportHooks g_transport{};
PeerResolver g_peer_resolver = nullptr;
bool g_matching2_enabled = false;
u32 g_mm_server_addr = 0;
u16 g_mm_server_udp_port = 0;
} // namespace
void SetTransportHooks(const TransportHooks& hooks) {
g_transport = hooks;
}
int SignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return g_transport.signaling_send ? g_transport.signaling_send(data, len, dest_addr, dest_port)
: -1;
}
int SignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return g_transport.signaling_recv ? g_transport.signaling_recv(buf, len, from_addr, from_port)
: -1;
}
int ControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return g_transport.control_send ? g_transport.control_send(data, len, dest_addr, dest_port)
: -1;
}
int ControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return g_transport.control_recv ? g_transport.control_recv(buf, len, from_addr, from_port) : -1;
}
bool TransportIsReady() {
return g_transport.transport_ready ? g_transport.transport_ready() : false;
}
u16 ConfiguredPort() {
return g_transport.configured_port ? g_transport.configured_port() : 0;
}
u32 AdvertisedAddr() {
return g_transport.advertised_addr ? g_transport.advertised_addr() : 0;
}
bool EnsureTransport() {
return g_transport.ensure_transport ? g_transport.ensure_transport() : TransportIsReady();
}
void SetPeerResolver(PeerResolver fn) {
g_peer_resolver = fn;
}
void SetMatching2Enabled(bool enabled) {
g_matching2_enabled = enabled;
}
void SetMmServerEndpoint(u32 addr, u16 udp_port) {
g_mm_server_addr = addr;
g_mm_server_udp_port = udp_port;
}
bool ResolvePeer(std::string_view online_id, u32* out_addr, u16* out_port) {
return g_peer_resolver ? g_peer_resolver(online_id, out_addr, out_port) : false;
}
bool Matching2Enabled() {
return g_matching2_enabled;
}
u32 MmServerAddr() {
return g_mm_server_addr;
}
u16 MmServerUdpPort() {
return g_mm_server_udp_port;
}
} // namespace Libraries::Np::NpSignaling::Stubs
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "common/types.h"
namespace Libraries::Np::NpSignaling::Stubs {
struct TransportHooks {
int (*signaling_send)(const void* data, u32 len, u32 dest_addr, u16 dest_port) = nullptr;
int (*signaling_recv)(void* buf, u32 len, u32* from_addr, u16* from_port) = nullptr;
int (*control_send)(const void* data, u32 len, u32 dest_addr, u16 dest_port) = nullptr;
int (*control_recv)(void* buf, u32 len, u32* from_addr, u16* from_port) = nullptr;
bool (*transport_ready)() = nullptr;
u16 (*configured_port)() = nullptr;
u32 (*advertised_addr)() = nullptr;
bool (*ensure_transport)() = nullptr;
};
void SetTransportHooks(const TransportHooks& hooks);
int SignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int SignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int ControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int ControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
bool TransportIsReady();
u16 ConfiguredPort();
u32 AdvertisedAddr();
bool EnsureTransport();
using PeerResolver = bool (*)(std::string_view online_id, u32* out_addr, u16* out_port);
void SetPeerResolver(PeerResolver fn);
void SetMatching2Enabled(bool enabled);
void SetMmServerEndpoint(u32 addr, u16 udp_port);
bool ResolvePeer(std::string_view online_id, u32* out_addr, u16* out_port);
bool Matching2Enabled();
u32 MmServerAddr();
u16 MmServerUdpPort();
} // namespace Libraries::Np::NpSignaling::Stubs
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2025 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_sns_facebook_dialog.h"
#include "core/libraries/system/commondialog.h"
namespace Libraries::Np::NpSnsFacebookDialog {
Libraries::CommonDialog::Status PS4_SYSV_ABI sceNpSnsFacebookDialogUpdateStatus() {
LOG_ERROR(Lib_NpSnsFacebookDialog, "(STUBBED) called");
return Libraries::CommonDialog::Status::NONE;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("fjV7C8H0Y8k", "libSceNpSnsFacebookDialog", 1, "libSceNpSnsFacebookDialog",
sceNpSnsFacebookDialogUpdateStatus);
};
} // namespace Libraries::Np::NpSnsFacebookDialog
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpSnsFacebookDialog {
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpSnsFacebookDialog
File diff suppressed because it is too large. Load diff
+229
View File
@@ -0,0 +1,229 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <core/libraries/system/userservice.h>
#include "common/types.h"
#include "core/libraries/rtc/rtc.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpTrophy {
constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;
constexpr int ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_NAME_MAX_SIZE = 128;
constexpr int ORBIS_NP_TROPHY_DESCR_MAX_SIZE = 1024;
constexpr int ORBIS_NP_TROPHY_NUM_MAX = 128;
constexpr int ORBIS_NP_TROPHY_INVALID_HANDLE = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_CONTEXT = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_TROPHY_ID = -1;
using OrbisNpTrophyHandle = s32;
using OrbisNpTrophyContext = s32;
using OrbisNpTrophyId = s32;
using OrbisNpTrophyFlagMask = u32;
struct OrbisNpTrophyFlagArray {
static constexpr int NumMasks = ORBIS_NP_TROPHY_FLAG_SETSIZE >> ORBIS_NP_TROPHY_FLAG_BITS_SHIFT;
std::array<OrbisNpTrophyFlagMask, NumMasks> flag_bits;
};
void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p);
void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p);
bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p);
struct OrbisNpTrophyData {
size_t size;
OrbisNpTrophyId trophy_id;
bool unlocked;
u8 reserved[3];
Rtc::OrbisRtcTick timestamp;
};
using OrbisNpTrophyGrade = s32;
constexpr int ORBIS_NP_TROPHY_GRADE_UNKNOWN = 0;
constexpr int ORBIS_NP_TROPHY_GRADE_PLATINUM = 1;
constexpr int ORBIS_NP_TROPHY_GRADE_GOLD = 2;
constexpr int ORBIS_NP_TROPHY_GRADE_SILVER = 3;
constexpr int ORBIS_NP_TROPHY_GRADE_BRONZE = 4;
using OrbisNpTrophyGroupId = s32;
constexpr int ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID = -1;
constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2;
struct OrbisNpTrophyDetails {
size_t size;
OrbisNpTrophyId trophy_id;
OrbisNpTrophyGrade trophy_grade;
OrbisNpTrophyGroupId group_id;
bool hidden;
u8 reserved[3];
char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE];
char description[ORBIS_NP_TROPHY_DESCR_MAX_SIZE];
};
struct OrbisNpTrophyGameData {
size_t size;
u32 unlocked_trophies;
u32 unlocked_platinum;
u32 unlocked_gold;
u32 unlocked_silver;
u32 unlocked_bronze;
u32 progress_percentage;
};
struct OrbisNpTrophyGameDetails {
size_t size;
u32 num_groups;
u32 num_trophies;
u32 num_platinum;
u32 num_gold;
u32 num_silver;
u32 num_bronze;
char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE];
};
struct OrbisNpTrophyGroupData {
size_t size;
OrbisNpTrophyGroupId group_id;
u32 unlocked_trophies;
u32 unlocked_platinum;
u32 unlocked_gold;
u32 unlocked_silver;
u32 unlocked_bronze;
u32 progress_percentage;
uint8_t reserved[4];
};
struct OrbisNpTrophyGroupDetails {
size_t size;
OrbisNpTrophyGroupId group_id;
u32 num_trophies;
u32 num_platinum;
u32 num_gold;
u32 num_silver;
u32 num_bronze;
char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
};
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion();
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails();
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature();
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
Libraries::UserService::OrbisUserServiceUserId user_id,
u32 service_label, u64 options);
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle);
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context);
s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
void* buffer, u64* size);
int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGameDetails* details,
OrbisNpTrophyGameData* data);
int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId, void* buffer, u64* size);
int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyGroupId groupId,
OrbisNpTrophyGroupDetails* details,
OrbisNpTrophyGroupData* data);
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, void* buffer, u64* size);
int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details,
OrbisNpTrophyData* data);
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle,
OrbisNpTrophyFlagArray* flags, u32* count);
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum();
int PS4_SYSV_ABI sceNpTrophyIntAbortHandle();
int PS4_SYSV_ABI sceNpTrophyIntCheckNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophyIntCreateHandle();
int PS4_SYSV_ABI sceNpTrophyIntDestroyHandle();
int PS4_SYSV_ABI sceNpTrophyIntGetLocalTrophySummary();
int PS4_SYSV_ABI sceNpTrophyIntGetProgress();
int PS4_SYSV_ABI sceNpTrophyIntGetRunningTitle();
int PS4_SYSV_ABI sceNpTrophyIntGetRunningTitles();
int PS4_SYSV_ABI sceNpTrophyIntGetTrpIconByUri();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitle();
int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal();
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle, uint64_t options);
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray();
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum();
int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle);
int PS4_SYSV_ABI sceNpTrophySystemAbortHandle();
int PS4_SYSV_ABI sceNpTrophySystemBuildGroupIconUri();
int PS4_SYSV_ABI sceNpTrophySystemBuildNetTrophyIconUri();
int PS4_SYSV_ABI sceNpTrophySystemBuildTitleIconUri();
int PS4_SYSV_ABI sceNpTrophySystemBuildTrophyIconUri();
int PS4_SYSV_ABI sceNpTrophySystemCheckNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophySystemCheckRecoveryRequired();
int PS4_SYSV_ABI sceNpTrophySystemCloseStorage();
int PS4_SYSV_ABI sceNpTrophySystemCreateContext();
int PS4_SYSV_ABI sceNpTrophySystemCreateHandle();
int PS4_SYSV_ABI sceNpTrophySystemDbgCtl();
int PS4_SYSV_ABI sceNpTrophySystemDebugLockTrophy();
int PS4_SYSV_ABI sceNpTrophySystemDebugUnlockTrophy();
int PS4_SYSV_ABI sceNpTrophySystemDestroyContext();
int PS4_SYSV_ABI sceNpTrophySystemDestroyHandle();
int PS4_SYSV_ABI sceNpTrophySystemDestroyTrophyConfig();
int PS4_SYSV_ABI sceNpTrophySystemGetDbgParam();
int PS4_SYSV_ABI sceNpTrophySystemGetDbgParamInt();
int PS4_SYSV_ABI sceNpTrophySystemGetGroupIcon();
int PS4_SYSV_ABI sceNpTrophySystemGetLocalTrophySummary();
int PS4_SYSV_ABI sceNpTrophySystemGetNextTitleFileEntryStatus();
int PS4_SYSV_ABI sceNpTrophySystemGetProgress();
int PS4_SYSV_ABI sceNpTrophySystemGetTitleFileStatus();
int PS4_SYSV_ABI sceNpTrophySystemGetTitleIcon();
int PS4_SYSV_ABI sceNpTrophySystemGetTitleSyncStatus();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyConfig();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyData();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyGroupData();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyIcon();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyTitleData();
int PS4_SYSV_ABI sceNpTrophySystemGetTrophyTitleIds();
int PS4_SYSV_ABI sceNpTrophySystemGetUserFileInfo();
int PS4_SYSV_ABI sceNpTrophySystemGetUserFileStatus();
int PS4_SYSV_ABI sceNpTrophySystemIsServerAvailable();
int PS4_SYSV_ABI sceNpTrophySystemNetSyncTitle();
int PS4_SYSV_ABI sceNpTrophySystemNetSyncTitles();
int PS4_SYSV_ABI sceNpTrophySystemOpenStorage();
int PS4_SYSV_ABI sceNpTrophySystemPerformRecovery();
int PS4_SYSV_ABI sceNpTrophySystemRemoveAll();
int PS4_SYSV_ABI sceNpTrophySystemRemoveTitleData();
int PS4_SYSV_ABI sceNpTrophySystemRemoveUserData();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParam();
int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt();
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId);
int PS4_SYSV_ABI Func_149656DA81D41C59();
int PS4_SYSV_ABI Func_9F80071876FFA5F6();
int PS4_SYSV_ABI Func_F8EF6F5350A91990();
int PS4_SYSV_ABI Func_FA7A2DD770447552();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpTrophy
File diff suppressed because it is too large. Load diff
+87
View File
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/rtc/rtc.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpTus {
using OrbisNpTssSlotId = s32;
using OrbisNpTusSlotId = s32;
struct OrbisNpTusVariable {
OrbisNpId npId;
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
u8 pad1[4];
OrbisNpId lastChangedAuthor;
s64 variable;
s64 oldVariable;
OrbisNpAccountId owner;
OrbisNpAccountId lastChangedAuthorId;
};
struct OrbisNpTusDataInfo {
u64 size;
u8 data[384];
};
struct OrbisNpTusDataStatus {
OrbisNpId npId;
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
OrbisNpId lastChangedAuthor;
u8 pad2[4];
void* data;
u64 dataSize;
OrbisNpTusDataInfo info;
};
static_assert(sizeof(OrbisNpTusDataStatus) == 0x1F0);
struct OrbisNpTusDataStatusA {
OrbisNpOnlineId onlineId;
u8 pad[16];
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
OrbisNpOnlineId lastChangedAuthor;
u8 pad2[20];
void* data;
u64 dataSize;
OrbisNpTusDataInfo info;
OrbisNpAccountId owner;
OrbisNpAccountId lastChangedAuthorId;
u8 pad3[16];
};
static_assert(sizeof(OrbisNpTusDataStatusA) == 0x210);
enum class OrbisNpTssStatus : int {
Ok = 0,
Partial = 1,
NotModified = 2,
};
struct OrbisNpTssDataStatus {
Libraries::Rtc::OrbisRtcTick modified;
OrbisNpTssStatus status;
u64 contentLength;
};
struct OrbisNpTssGetDataOptParam {
u64 size;
u64* offset;
u64* last;
void* param;
};
s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpTus
+92
View File
@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <cstring>
#include <string_view>
#include "common/types.h"
#include "core/libraries/error_codes.h"
// For structs and constants shared between multiple Np libraries.
namespace Libraries::Np {
using OrbisNpAccountId = u64;
struct OrbisNpTitleId {
char id[13];
u8 padding[3];
};
struct OrbisNpTitleSecret {
u8 data[128];
};
constexpr s32 ORBIS_NP_ONLINEID_MAX_LENGTH = 16;
struct OrbisNpOnlineId {
char data[ORBIS_NP_ONLINEID_MAX_LENGTH];
s8 term;
s8 dummy[3];
};
struct OrbisNpId {
OrbisNpOnlineId handle;
u8 opt[8];
u8 reserved[8];
};
inline void SetNpOnlineId(OrbisNpOnlineId& dst, std::string_view src) {
dst = {};
const size_t len = std::min(src.size(), static_cast<size_t>(ORBIS_NP_ONLINEID_MAX_LENGTH));
if (len > 0) {
std::memcpy(dst.data, src.data(), len);
}
dst.term = 0;
}
inline void SetNpId(OrbisNpId& dst, std::string_view online_id) {
dst = {};
SetNpOnlineId(dst.handle, online_id);
}
struct OrbisNpClientId {
char id[129];
u8 padding[7];
};
struct OrbisNpAuthorizationCode {
char code[129];
u8 padding[7];
};
struct OrbisNpClientSecret {
char secret[257];
u8 padding[7];
};
struct OrbisNpIdToken {
char token[4097];
u8 padding[7];
};
using OrbisNpServiceLabel = u32;
constexpr s32 ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF;
using OrbisNpAccountId = u64;
enum OrbisNpPlatformType : s32 {
None = 0,
PS3 = 1,
Vita = 2,
PS4 = 3,
};
struct OrbisNpPeerAddressA {
OrbisNpAccountId accountId;
OrbisNpPlatformType platform;
char padding[4];
};
}; // namespace Libraries::Np
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "common/types.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpWebApi {
#define ORBIS_NP_WEBAPI_DEFAULT_CONNECTION_NUM 1
#define ORBIS_NP_WEBAPI_MAX_CONNECTION_NUM 16
#define ORBIS_NP_WEBAPI_PUSH_EVENT_DATA_TYPE_LEN_MAX 64
#define ORBIS_NP_WEBAPI_EXTD_PUSH_EVENT_EXTD_DATA_KEY_LEN_MAX 32
struct OrbisNpWebApiPushEventDataType {
char val[ORBIS_NP_WEBAPI_PUSH_EVENT_DATA_TYPE_LEN_MAX + 1];
};
struct OrbisNpWebApiExtdPushEventExtdDataKey {
char val[ORBIS_NP_WEBAPI_EXTD_PUSH_EVENT_EXTD_DATA_KEY_LEN_MAX + 1];
};
struct OrbisNpWebApiPushEventFilterParameter {
OrbisNpWebApiPushEventDataType dataType;
};
struct OrbisNpWebApiServicePushEventFilterParameter {
OrbisNpWebApiPushEventDataType dataType;
};
struct OrbisNpWebApiExtdPushEventFilterParameter {
OrbisNpWebApiPushEventDataType dataType;
OrbisNpWebApiExtdPushEventExtdDataKey* pExtdDataKey;
u64 extdDataKeyNum;
};
struct OrbisNpWebApiExtdPushEventExtdData {
OrbisNpWebApiExtdPushEventExtdDataKey extdDataKey;
char* pData;
u64 dataLen;
};
struct OrbisNpWebApiHttpHeader {
char* pName;
char* pValue;
};
struct OrbisNpWebApiMultipartPartParameter {
OrbisNpWebApiHttpHeader* pHeaders;
u64 headerNum;
u64 contentLength;
};
enum OrbisNpWebApiHttpMethod : s32 {
ORBIS_NP_WEBAPI_HTTP_METHOD_GET,
ORBIS_NP_WEBAPI_HTTP_METHOD_POST,
ORBIS_NP_WEBAPI_HTTP_METHOD_PUT,
ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE,
ORBIS_NP_WEBAPI_HTTP_METHOD_PATCH
};
struct OrbisNpWebApiContentParameter {
u64 contentLength;
const char* pContentType;
u8 reserved[16];
};
struct OrbisNpWebApiResponseInformationOption {
s32 httpStatus;
char* pErrorObject;
u64 errorObjectSize;
u64 responseDataSize;
};
struct OrbisNpWebApiMemoryPoolStats {
u64 poolSize;
u64 maxInuseSize;
u64 currentInuseSize;
s32 reserved;
};
struct OrbisNpWebApiConnectionStats {
u32 max;
u32 used;
u32 unused;
u32 keepAlive;
u64 reserved;
};
struct OrbisNpWebApiIntInitializeArgs {
u32 libHttpCtxId;
u8 reserved[4];
u64 poolSize;
const char* name;
u64 structSize;
};
struct OrbisNpWebApiIntCreateRequestExtraArgs {
void* unk_0;
void* unk_1;
void* unk_2;
};
using OrbisNpWebApiPushEventCallback = PS4_SYSV_ABI void (*)(); // dummy
using OrbisNpWebApiExtdPushEventCallback = PS4_SYSV_ABI void (*)(); // dummy
using OrbisNpWebApiExtdPushEventCallbackA = PS4_SYSV_ABI void (*)(
s32 userCtxId, s32 callbackId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel,
const OrbisNpPeerAddressA* pTo, const OrbisNpOnlineId* pToOnlineId,
const OrbisNpPeerAddressA* pFrom, const OrbisNpOnlineId* pFromOnlineId,
const OrbisNpWebApiPushEventDataType* pDataType, const char* pData, u64 dataLen,
const OrbisNpWebApiExtdPushEventExtdData* pExtdData, u64 extdDataNum, void* pUserArg);
using OrbisNpWebApiServicePushEventCallback = PS4_SYSV_ABI void (*)(); // dummy
using OrbisNpWebApiInternalServicePushEventCallback = PS4_SYSV_ABI void (*)(); // dummy
using OrbisNpWebApiInternalServicePushEventCallbackA = PS4_SYSV_ABI void (*)(); // dummy
using OrbisNpWebApiNotificationCallback = PS4_SYSV_ABI void (*)(); // dummy
// --- Push-event delivery (emulator-internal) ---------------------------------
// A producer (NpHandler / shadNet notification ingress) calls EnqueuePushEvent from
// any thread. The event is queued and dispatched to registered push-event callbacks
// on the game's thread during sceNpCheckCallback.This mirrors the real lib, where push events
// arrive via the NP push/notification subsystem and are pumped on the callback-check thread.
struct PushEventInput {
Libraries::UserService::OrbisUserServiceUserId targetUserId = 0;
std::string npServiceName; // e.g. "np:service:..."; empty for basic push
OrbisNpServiceLabel npServiceLabel = 0;
std::string dataType; // OrbisNpWebApiPushEventDataType value
std::string data; // payload bytes
OrbisNpOnlineId fromOnlineId{};
bool hasFrom = false;
OrbisNpOnlineId toOnlineId{};
bool hasTo = false;
std::vector<std::pair<std::string, std::string>> extdData; // (key,value) for extd push
};
void EnqueuePushEvent(const PushEventInput& ev);
s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize);
s32 PS4_SYSV_ABI sceNpWebApiTerminate(s32 libCtxId);
s32 PS4_SYSV_ABI sceNpWebApiCreateContextA(s32 libCtxId,
Libraries::UserService::OrbisUserServiceUserId userId);
s32 PS4_SYSV_ABI sceNpWebApiDeleteContext(s32 titleUserCtxId);
s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(s32 titleUserCtxId, const char* pApiGroup,
const char* pPath, OrbisNpWebApiHttpMethod method,
const OrbisNpWebApiContentParameter* pContentParameter,
s64* pRequestId);
s32 PS4_SYSV_ABI sceNpWebApiSendRequest2(s64 requestId, const void* pData, u64 dataSize,
OrbisNpWebApiResponseInformationOption* pRespInfoOption);
s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(s64 requestId);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,334 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "np_web_api.h"
namespace Libraries::Np::NpWebApi {
// Structs reference each other, so declare them before their contents.
struct OrbisNpWebApiContext;
struct OrbisNpWebApiUserContext;
struct OrbisNpWebApiRequest;
struct OrbisNpWebApiHandle;
struct OrbisNpWebApiTimerHandle;
struct OrbisNpWebApiPushEventFilter;
struct OrbisNpWebApiServicePushEventFilter;
struct OrbisNpWebApiExtendedPushEventFilter;
struct OrbisNpWebApiRegisteredPushEventCallback;
struct OrbisNpWebApiRegisteredServicePushEventCallback;
struct OrbisNpWebApiRegisteredExtendedPushEventCallback;
struct OrbisNpWebApiContext {
s32 type;
s32 userCount;
s32 libCtxId;
s32 libHttpCtxId;
std::recursive_mutex contextLock;
std::map<s32, OrbisNpWebApiUserContext*> userContexts;
std::map<s32, OrbisNpWebApiHandle*> handles;
std::map<s32, OrbisNpWebApiTimerHandle*> timerHandles;
std::map<s32, OrbisNpWebApiPushEventFilter*> pushEventFilters;
std::map<s32, OrbisNpWebApiServicePushEventFilter*> servicePushEventFilters;
std::map<s32, OrbisNpWebApiExtendedPushEventFilter*> extendedPushEventFilters;
std::string name;
bool terminated;
};
struct OrbisNpWebApiUserContext {
OrbisNpWebApiContext* parentContext;
s32 userCount;
s32 userCtxId;
Libraries::UserService::OrbisUserServiceUserId userId;
std::map<s64, OrbisNpWebApiRequest*> requests;
std::map<s32, OrbisNpWebApiRegisteredPushEventCallback*> pushEventCallbacks;
std::map<s32, OrbisNpWebApiRegisteredServicePushEventCallback*> servicePushEventCallbacks;
std::map<s32, OrbisNpWebApiRegisteredExtendedPushEventCallback*> extendedPushEventCallbacks;
bool deleted;
OrbisNpWebApiNotificationCallback notificationCallbackFunction;
void* pNotificationCallbackUserArgs;
};
struct OrbisNpWebApiRequest {
OrbisNpWebApiContext* parentContext;
s32 userCount;
s64 requestId;
std::string userApiGroup;
std::string userPath;
OrbisNpWebApiHttpMethod userMethod;
u64 userContentLength;
std::string userContentType;
// App-supplied request headers (sceNpWebApiAddHttpRequestHeader), replayed onto
// the libSceHttp request in the send path after the lib-managed CT/Authorization.
std::vector<std::pair<std::string, std::string>> userHeaders;
bool multipart;
// Multipart body assembly (sceNpWebApiSetMultipartContentType / AddMultipartPart /
// SendMultipartRequest). Parts accumulate across sends,the body is framed once all are in.
struct MultipartPart {
std::string rawHeaders; // "<name>: <value>\r\n...Content-Length: <n>\r\n\r\n"
u64 contentLength = 0;
std::string data;
u64 sentSize = 0;
};
std::string multipartContentType; // type name, e.g. "multipart/mixed"
std::string multipartBoundary;
std::vector<MultipartPart> multipartParts;
bool aborted;
bool sent;
u32 requestTimeout;
u64 requestEndTime;
bool timedOut;
// not sure Stephen
u8 requestState;
u64 remainingData;
u32 readOffset;
char data[64];
s32 http_connection_id = 0;
s32 http_request_id = 0;
s32 http_template_id = 0;
};
struct OrbisNpWebApiHandle {
s32 handleId;
bool aborted;
bool deleted;
s32 userCount;
};
struct OrbisNpWebApiTimerHandle {
s32 handleId;
u32 handleTimeout;
u64 handleEndTime;
bool timedOut;
};
struct OrbisNpWebApiPushEventFilter {
s32 filterId;
std::vector<OrbisNpWebApiPushEventFilterParameter> filterParams;
OrbisNpWebApiContext* parentContext;
};
struct OrbisNpWebApiServicePushEventFilter {
s32 filterId;
bool internal;
std::vector<OrbisNpWebApiServicePushEventFilterParameter> filterParams;
std::string npServiceName;
OrbisNpServiceLabel npServiceLabel;
OrbisNpWebApiContext* parentContext;
};
struct OrbisNpWebApiExtendedPushEventFilter {
s32 filterId;
bool internal;
std::vector<OrbisNpWebApiExtdPushEventFilterParameter> filterParams;
std::string npServiceName;
OrbisNpServiceLabel npServiceLabel;
OrbisNpWebApiContext* parentContext;
};
struct OrbisNpWebApiRegisteredPushEventCallback {
s32 callbackId;
s32 filterId;
OrbisNpWebApiPushEventCallback cbFunc;
void* pUserArg;
};
struct OrbisNpWebApiRegisteredServicePushEventCallback {
s32 callbackId;
s32 filterId;
OrbisNpWebApiServicePushEventCallback cbFunc;
OrbisNpWebApiInternalServicePushEventCallback internalCbFunc;
// Note: real struct stores both internal callbacks in one field
OrbisNpWebApiInternalServicePushEventCallbackA internalCbFuncA;
void* pUserArg;
};
struct OrbisNpWebApiRegisteredExtendedPushEventCallback {
s32 callbackId;
s32 filterId;
OrbisNpWebApiExtdPushEventCallback cbFunc;
// Note: real struct stores both callbacks in one field
OrbisNpWebApiExtdPushEventCallbackA cbFuncA;
void* pUserArg;
};
// General functions
s32 initializeLibrary(); // FUN_01001450
s32 getCompiledSdkVersion(); // FUN_01001440
// Library context functions
s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name,
s32 type); // FUN_01006970
OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag = 0); // FUN_01006860
void releaseContext(OrbisNpWebApiContext* context); // FUN_01006fc0
bool isContextTerminated(OrbisNpWebApiContext* context); // FUN_01006910
bool isContextBusy(OrbisNpWebApiContext* context); // FUN_01008a50
bool areContextHandlesBusy(OrbisNpWebApiContext* context); // FUN_01008c20
void lockContext(OrbisNpWebApiContext* context); // FUN_010072e0
void unlockContext(OrbisNpWebApiContext* context); // FUN_010072f0
void markContextAsTerminated(OrbisNpWebApiContext* context); // FUN_01008bf0
void checkContextTimeout(OrbisNpWebApiContext* context); // FUN_01008ad0
void checkTimeout(); // FUN_01003700
s32 deleteContext(s32 libCtxId); // FUN_01006c70
s32 terminateContext(s32 libCtxId); // FUN_010014b0
// User context functions
OrbisNpWebApiUserContext* findUserContextByUserId(
OrbisNpWebApiContext* context,
Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010075c0
OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context,
s32 userCtxId); // FUN_01007530
s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId); // FUN_010016a0
s32 createUserContext(s32 libCtxId,
Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010015c0
s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc,
void* pUserArg); // FUN_01003770
s32 unregisterNotificationCallback(s32 titleUserCtxId); // FUN_01003800
bool isUserContextBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100ea40
bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100d1f0
void releaseUserContext(OrbisNpWebApiUserContext* userContext); // FUN_0100caa0
void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext); // FUN_0100ea90
s32 deleteUserContext(s32 userCtxId); // FUN_01001710
// Request functions
s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath,
OrbisNpWebApiHttpMethod method,
const OrbisNpWebApiContentParameter* pContentParameter,
const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId,
bool isMultipart); // FUN_01001850
OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext,
s64 requestId); // FUN_0100d3a0
OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext,
s64 requestId); // FUN_0100d330
bool isRequestBusy(OrbisNpWebApiRequest* request); // FUN_0100c1b0
s32 setRequestTimeout(s64 requestId, u32 timeout); // FUN_01003610
void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0
void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130
s32 sendRequest(s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag,
OrbisNpWebApiResponseInformationOption* pResponseInformationOption); // FUN_01001c50
s32 setMultipartContentType(s64 requestId, const char* pTypeName, const char* pBoundary);
s32 addMultipartPart(s64 requestId, const OrbisNpWebApiMultipartPartParameter* pParam, s32* pIndex);
s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext,
OrbisNpWebApiRequest* request); // FUN_01001b70
s32 abortRequest(s64 requestId); // FUN_01002c70
void releaseRequest(OrbisNpWebApiRequest* request); // FUN_01009fb0
s32 deleteRequest(s64 requestId); // FUN_010019a0
// Request/response header helpers
s32 addHttpRequestHeaderInternal(s64 requestId, const char* pFieldName, const char* pValue);
s32 getHttpResponseHeaderValueInternal(s64 requestId, const char* pFieldName, char* pValue,
u64 valueSize, u64* pValueLength);
// Returns the WebApi error code captured from the most recent error response body
// read on this thread (sceNpWebApiGetErrorCode). 0 if none.
s32 getLastWebApiError();
// Game-thread pump: drains queued push events (EnqueuePushEvent) and dispatches them
// to matching registered callbacks. Registered into the NpManager callback pump in
// RegisterLib so it runs during sceNpCheckCallback.
void DrainPushEvents();
// Handle functions
s32 createHandleInternal(OrbisNpWebApiContext* context); // FUN_01007730
s32 createHandle(s32 libCtxId); // FUN_01002ee0
s32 setHandleTimeoutInternal(OrbisNpWebApiContext* context, s32 handleId,
u32 timeout); // FUN_01007ed0
s32 setHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout); // FUN_010036b0
void startHandleTimer(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007fd0
void releaseHandle(OrbisNpWebApiContext* context, OrbisNpWebApiHandle* handle); // FUN_01007ea0
s32 getHandle(OrbisNpWebApiContext* context, s32 handleId,
OrbisNpWebApiHandle** handleOut); // FUN_01007e20
s32 abortHandle(s32 libCtxId, s32 handleId); // FUN_01003390
s32 deleteHandleInternal(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007a00
s32 deleteHandle(s32 libCtxId, s32 handleId); // FUN_01002f20
// Push event filter functions
s32 createPushEventFilterInternal(OrbisNpWebApiContext* context,
const OrbisNpWebApiPushEventFilterParameter* pFilterParam,
u64 filterParamNum); // FUN_01008040
s32 createPushEventFilter(s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam,
u64 filterParamNum); // FUN_01002d10
s32 deletePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId); // FUN_01008180
s32 deletePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002d60
// Push event callback functions
s32 registerPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId,
OrbisNpWebApiPushEventCallback cbFunc,
void* userArg); // FUN_0100d450
s32 registerPushEventCallback(s32 titleUserCtxId, s32 filterId,
OrbisNpWebApiPushEventCallback cbFunc,
void* pUserArg); // FUN_01002da0
s32 unregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01002e50
// Service push event filter functions
s32 createServicePushEventFilterInternal(
OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName,
OrbisNpServiceLabel npServiceLabel,
const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam,
u64 filterParamNum); // FUN_010082f0
s32 createServicePushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName,
OrbisNpServiceLabel npServiceLabel,
const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam,
u64 filterParamNum); // FUN_01002f60
s32 deleteServicePushEventFilterInternal(OrbisNpWebApiContext* context,
s32 filterId); // FUN_010084f0
s32 deleteServicePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002fe0
// Service push event callback functions
s32 registerServicePushEventCallbackInternal(
OrbisNpWebApiUserContext* userContext, s32 filterId,
OrbisNpWebApiServicePushEventCallback cbFunc,
OrbisNpWebApiInternalServicePushEventCallback intCbFunc,
OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, void* pUserArg); // FUN_0100d8c0
s32 registerServicePushEventCallback(s32 titleUserCtxId, s32 filterId,
OrbisNpWebApiServicePushEventCallback cbFunc,
OrbisNpWebApiInternalServicePushEventCallback intCbFunc,
OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA,
void* pUserArg); // FUN_01003030
s32 unregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_010030f0
// Extended push event filter functions
s32 createExtendedPushEventFilterInternal(
OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName,
OrbisNpServiceLabel npServiceLabel,
const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum,
bool internal); // FUN_01008680
s32 createExtendedPushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName,
OrbisNpServiceLabel npServiceLabel,
const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam,
u64 filterParamNum, bool internal); // FUN_01003180
s32 deleteExtendedPushEventFilterInternal(OrbisNpWebApiContext* context,
s32 filterId); // FUN_01008880
s32 deleteExtendedPushEventFilter(s32 libCtxId, s32 filterId); // FUN_01003200
// Extended push event callback functions
s32 registerExtdPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId,
OrbisNpWebApiExtdPushEventCallback cbFunc,
OrbisNpWebApiExtdPushEventCallbackA cbFuncA,
void* pUserArg); // FUN_0100df60
s32 registerExtdPushEventCallback(s32 userCtxId, s32 filterId,
OrbisNpWebApiExtdPushEventCallback cbFunc,
OrbisNpWebApiExtdPushEventCallbackA cbFuncA,
void* pUserArg); // FUN_01003250
s32 registerExtdPushEventCallbackA(s32 userCtxId, s32 filterId,
OrbisNpWebApiExtdPushEventCallbackA cbFunc,
void* pUserArg); // FUN_01003240
s32 unregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01003300
s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code);
s32 PS4_SYSV_ABI getHttpRequestIdFromRequest(OrbisNpWebApiRequest* request);
s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size);
void PS4_SYSV_ABI setRequestEndTime(OrbisNpWebApiRequest* request);
void PS4_SYSV_ABI clearRequestEndTime(OrbisNpWebApiRequest* req);
bool PS4_SYSV_ABI hasRequestTimedOut(OrbisNpWebApiRequest* request);
void PS4_SYSV_ABI setRequestState(OrbisNpWebApiRequest* request, u8 state);
u64 PS4_SYSV_ABI copyRequestData(OrbisNpWebApiRequest* request, void* data, u64 size);
}; // namespace Libraries::Np::NpWebApi
@@ -0,0 +1,522 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#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_manager.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpWebApi2 {
s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}", request_id);
return abortRequest(request_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader(s64 request_id, const char* field_name,
const char* field_value) {
if (!field_name || !field_value) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, field_name = {}, field_value = {}",
request_id, field_name, field_value);
return addHttpRequestHeader(request_id, field_name, field_value);
}
s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag(s64 request_id, const char* value) {
if (!value) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, value = {}", request_id, value);
return addHttpRequestHeader(request_id, "X-Psn-WebTrace-Tag", value);
}
void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() {
LOG_TRACE(Lib_NpWebApi2, "called");
return checkTimeout();
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest(s32 user_ctx_id, const char* api_group,
const char* path, const char* method,
s64* request_id) {
if (!api_group || !path || !method) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, api_group = {}, path = {}, method = {}",
user_ctx_id, api_group, path, method);
s64 temp_request_id = 0;
s32 result =
createRequest(user_ctx_id, api_group, path, method, nullptr, true, &temp_request_id);
if (result == 0) {
LOG_INFO(Lib_NpWebApi2, "created request_id = {:#x}", temp_request_id);
if (request_id) {
*request_id = temp_request_id;
}
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest(s32 user_ctx_id, const char* api_group, const char* path,
const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter,
s64* request_id) {
if (!api_group || !path || !method) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
if (content_parameter && content_parameter->content_length != 0 &&
!content_parameter->content_type) {
LOG_ERROR(Lib_NpWebApi2, "Invalid content parameter");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER;
}
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, api_group = {}, path = {}, method = {}",
user_ctx_id, api_group, path, method);
s64 temp_request_id = 0;
s32 result = createRequest(user_ctx_id, api_group, path, method, content_parameter, false,
&temp_request_id);
if (result == 0) {
LOG_INFO(Lib_NpWebApi2, "created request_id = {:#x}", temp_request_id);
if (request_id) {
*request_id = temp_request_id;
}
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id,
UserService::OrbisUserServiceUserId user_id) {
if (lib_ctx_id >= 0x8000) {
LOG_ERROR(Lib_NpWebApi2, "Invalid library context id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID;
}
if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
LOG_ERROR(Lib_NpWebApi2, "Invalid user id");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, user_id = {}", lib_ctx_id, user_id);
s32 user_ctx_id = createUserContext(lib_ctx_id, user_id);
if (user_ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created user_ctx_id = {:#x}", user_ctx_id);
}
return user_ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}", request_id);
return deleteRequest(request_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}", user_ctx_id);
return deleteUserContext(user_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue(s64 request_id, const char* field_name,
char* value, u64 value_size) {
if (!field_name || !value || value_size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, field_name = {}", request_id, field_name);
return getHttpResponseHeaderData(request_id, field_name, value, value_size, nullptr);
}
s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength(s64 request_id,
const char* field_name,
u64* field_value_length) {
if (!field_name || !field_value_length) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 result = getHttpResponseHeaderData(request_id, field_name, nullptr, 0, field_value_length);
if (result >= 0) {
LOG_INFO(Lib_NpWebApi2,
"on request_id = {:#x}, field_name = {} returned field_value_length = {:#x}",
request_id, field_name, *field_value_length);
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id,
OrbisNpWebApi2MemoryPoolStats* stats) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
return getMemoryPoolStats(lib_ctx_id, stats);
}
s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", lib_http_ctx_id,
pool_size);
// Uses a sceLncUtilGetAppStatus check to enable debug mode. For now, default to normal.
s32 ctx_id = createLibraryContext(lib_http_ctx_id, 1, pool_size, nullptr);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", lib_http_ctx_id,
pool_size);
s32 ctx_id = createLibraryContext(lib_http_ctx_id, 3, pool_size, nullptr);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) {
LOG_ERROR(Lib_NpWebApi2, "Invalid arguments");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = {}",
args->lib_http_ctx_id, args->pool_size, args->name ? args->name : "(null)");
s32 ctx_id = createLibraryContext(args->lib_http_ctx_id, 2, args->pool_size, args->name);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) {
if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) {
LOG_ERROR(Lib_NpWebApi2, "Invalid arguments");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(
Lib_NpWebApi2,
"called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = {}, push_config_group = {:#x}",
args->lib_http_ctx_id, args->pool_size, args->name ? args->name : "(null)",
args->push_config_group);
s32 ctx_id = createLibraryContext(args->lib_http_ctx_id, 2, args->pool_size, args->name);
if (ctx_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created lib_ctx_id = {:#x}", ctx_id);
}
return ctx_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle(s32 lib_ctx_id, s32 handle_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}", lib_ctx_id, handle_id);
return abortPushEventHandle(lib_ctx_id, handle_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter(
s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param, u64 filter_param_num) {
if ((np_service_name && np_service_label == ORBIS_NP_INVALID_SERVICE_LABEL) || !filter_param ||
filter_param_num == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2,
"called, lib_ctx_id = {:#x}, handle_id = {:#x}, np_service_name = {}, "
"np_service_label = {:#x}, filter_param_num = {:#x}",
lib_ctx_id, handle_id, np_service_name ? np_service_name : "(null)",
static_cast<u32>(np_service_label), filter_param_num);
for (u64 i = 0; i < filter_param_num; i++) {
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].data_type.val = {}", i,
filter_param[i].data_type.val);
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].extd_data_key_num = {}", i,
filter_param[i].extd_data_key_num);
for (u64 j = 0; j < filter_param[i].extd_data_key_num && filter_param[i].extd_data_key;
j++) {
LOG_DEBUG(Lib_NpWebApi2, "filter_param[{}].extd_data_key[{}].val = {}", i, j,
filter_param[i].extd_data_key[j].val);
}
}
s32 filter_id = createPushEventFilter(lib_ctx_id, handle_id, np_service_name, np_service_label,
filter_param, filter_param_num, false);
if (filter_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created filter_id = {:#x}", filter_id);
}
return filter_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle(s32 lib_ctx_id) {
LOG_DEBUG(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
s32 handle_id = createPushEventHandle(lib_ctx_id);
if (handle_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created handle_id = {:#x}", handle_id);
}
return handle_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext(
s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}", user_ctx_id);
s32 result = createPushContext(user_ctx_id, push_ctx_id);
if (result == 0) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "created push_ctx_id = {:#x}", raw_id);
}
return result;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter(s32 lib_ctx_id, s32 filter_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, filter_id = {:#x}", lib_ctx_id, filter_id);
return deletePushEventFilter(lib_ctx_id, filter_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle(s32 lib_ctx_id, s32 handle_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}", lib_ctx_id, handle_id);
return deletePushEventHandle(lib_ctx_id, handle_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext(
s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, push_ctx_id = {:#x}", user_ctx_id,
raw_id);
return deletePushContext(user_ctx_id, push_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg) {
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, filter_id = {:#x}", user_ctx_id,
filter_id);
if (!cb_func) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 callback_id = registerPushEventCallback(user_ctx_id, filter_id, cb_func, user_arg);
if (callback_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created callback_id = {:#x}", callback_id);
}
return callback_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback(
s32 user_ctx_id, s32 filter_id, OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
LOG_DEBUG(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, filter_id = {:#x}", user_ctx_id,
filter_id);
if (!cb_func) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s32 callback_id = registerPushContextCallback(user_ctx_id, filter_id, cb_func, user_arg);
if (callback_id > 0) {
LOG_INFO(Lib_NpWebApi2, "created callback_id = {:#x}", callback_id);
}
return callback_id;
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}, handle_id = {:#x}, timeout = {}",
lib_ctx_id, handle_id, timeout);
return setHandleTimeout(lib_ctx_id, handle_id, timeout);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback(
s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
if (!push_ctx_id) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, push_ctx_id = {:#x}", user_ctx_id,
raw_id);
return startPushContextCallback(user_ctx_id, push_ctx_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback(s32 user_ctx_id, s32 callback_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, callback_id = {:#x}", user_ctx_id,
callback_id);
return unregisterPushEventCallback(user_ctx_id, callback_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback(s32 user_ctx_id,
s32 callback_id) {
LOG_INFO(Lib_NpWebApi2, "called, user_ctx_id = {:#x}, callback_id = {:#x}", user_ctx_id,
callback_id);
return unregisterPushContextCallback(user_ctx_id, callback_id);
}
s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) {
if (!data || size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, size = {:#x}", request_id, size);
return readData(request_id, data, size);
}
s32 PS4_SYSV_ABI
sceNpWebApi2SendMultipartRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
if (part_index <= 0 || !data || data_size == 0) {
LOG_ERROR(Lib_NpWebApi2, "Invalid parameters");
return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT;
}
LOG_WARNING(Lib_NpWebApi2, "called, request_id = {:#x}, part_index = {}, data_size = {:#x}",
request_id, part_index, data_size);
return sendRequest(request_id, part_index, data, data_size, resp_info_option);
}
s32 PS4_SYSV_ABI
sceNpWebApi2SendRequest(s64 request_id, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, data_size = {:#x}", request_id, data_size);
return sendRequest(request_id, 0, data, data_size, resp_info_option);
}
s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) {
LOG_INFO(Lib_NpWebApi2, "called, request_id = {:#x}, timeout = {}", request_id, timeout);
return setRequestTimeout(request_id, timeout);
}
s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) {
LOG_INFO(Lib_NpWebApi2, "called, lib_ctx_id = {:#x}", lib_ctx_id);
return terminateLibraryContext(lib_ctx_id);
}
s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_03D22863300D2B73() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_97296F7578AAD541() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() {
LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
Libraries::Np::NpManager::RegisterNpCallback("npwebapi2_push", processPushEvents);
LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest);
LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddHttpRequestHeader);
LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddMultipartPart);
LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2AddWebTraceTag);
LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout);
LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateMultipartRequest);
LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest);
LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2CreateUserContext);
LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest);
LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2DeleteUserContext);
LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValue);
LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetHttpResponseHeaderValueLength);
LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2GetMemoryPoolStats);
LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize);
LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2InitializeForPresence);
LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntCreateRequest);
LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize);
LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntInitialize2);
LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2IntPushEventCreateCtxIndFilter);
LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventAbortHandle);
LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateFilter);
LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreateHandle);
LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventCreatePushContext);
LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteFilter);
LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeleteHandle);
LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventDeletePushContext);
LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterCallback);
LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventRegisterPushContextCallback);
LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventSetHandleTimeout);
LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventStartPushContextCallback);
LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterCallback);
LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2PushEventUnregisterPushContextCallback);
LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData);
LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SendMultipartRequest);
LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest);
LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetMultipartContentType);
LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2",
sceNpWebApi2SetRequestTimeout);
LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate);
LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620);
LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_03D22863300D2B73);
LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_97296F7578AAD541);
LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2",
Func_E0DF39A36F087DB9);
};
} // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpWebApi2 {
struct OrbisNpWebApi2IntInitializeArgs {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
const char* name;
u64 struct_size;
};
struct OrbisNpWebApi2IntInitialize2Args {
s32 lib_http_ctx_id;
s32 reserved;
u64 pool_size;
const char* name;
u32 push_config_group;
s32 reserved2;
u64 struct_size;
};
struct OrbisNpWebApi2ContentParameter {
u64 content_length;
const char* content_type;
u8 reserved[16];
};
struct OrbisNpWebApi2MemoryPoolStats {
u64 pool_size;
u64 max_inuse_size;
u64 current_inuse_size;
s32 reserved;
};
struct OrbisNpWebApi2ResponseInformationOption {
s32 http_status;
s32 reserved;
char* error_object;
u64 error_object_size;
u64 response_data_size;
};
struct OrbisNpWebApi2PushEventDataType {
char val[65];
};
struct OrbisNpWebApi2PushEventExtdDataKey {
char val[33];
};
struct OrbisNpWebApi2PushEventFilterParameter {
OrbisNpWebApi2PushEventDataType data_type;
OrbisNpWebApi2PushEventExtdDataKey* extd_data_key;
u64 extd_data_key_num;
};
struct OrbisNpWebApi2PushEventExtdData {
OrbisNpWebApi2PushEventExtdDataKey extd_data_key;
char* data;
u64 data_len;
};
using OrbisNpWebApi2PushEventCallback = void PS4_SYSV_ABI (*)(
s32 user_ctx_id, s32 callback_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label, const OrbisNpPeerAddressA* to,
const OrbisNpOnlineId* to_online_id, const OrbisNpPeerAddressA* from,
const OrbisNpOnlineId* from_online_id, const OrbisNpWebApi2PushEventDataType* data_type,
const char* data, u64 data_len, const OrbisNpWebApi2PushEventExtdData* extd_data,
u64 extd_data_num, void* user_arg);
enum OrbisNpWebApi2PushEventPushContextCallbackType {
Unknown = -1,
Received = 0,
Dropped = 1,
};
struct OrbisNpWebApi2PushEventPushContextId {
char uuid[37];
};
using OrbisNpWebApi2PushEventPushContextCallback = void PS4_SYSV_ABI (*)(
s32 user_ctx_id, s32 callback_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id,
OrbisNpWebApi2PushEventPushContextCallbackType cb_type, const char* np_service_name,
OrbisNpServiceLabel np_service_label, const OrbisNpPeerAddressA* to,
const OrbisNpOnlineId* to_online_id, const OrbisNpPeerAddressA* from,
const OrbisNpOnlineId* from_online_id, const OrbisNpWebApi2PushEventDataType* data_type,
const char* data, u64 data_len, const OrbisNpWebApi2PushEventExtdData* extd_data,
u64 extd_data_num, void* user_arg);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,630 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
namespace Libraries::Np::NpWebApi2 {
u32 g_current_push_event_handle_id{};
u32 g_current_push_event_filter_id{};
u32 g_current_push_event_callback_id{};
u32 g_current_push_context_callback_id{};
u32 g_current_user_context_id{};
u64 g_current_request_id{};
void LibraryContext::CheckTimeout() {
u64 time = Libraries::Kernel::sceKernelGetProcessTime();
std::scoped_lock lk{this->lock};
for (auto& [user_ctx_id, user_ctx] : this->user_contexts) {
user_ctx->CheckTimeout();
}
for (auto& [handle_id, handle] : this->push_event_handles) {
handle->CheckTimeout(time);
}
}
s32 LibraryContext::CreateUserContext(Libraries::UserService::OrbisUserServiceUserId user_id) {
std::scoped_lock lk{this->lock};
if (this->user_contexts.size() >= 0x10000) {
LOG_ERROR(Lib_NpWebApi2, "Too many user contexts");
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_MAX;
}
s32 actual_user_ctx_id = 0;
do {
g_current_user_context_id++;
if (g_current_user_context_id >= 0x10000) {
g_current_user_context_id = 1;
}
actual_user_ctx_id = (this->id << 0x10) | g_current_user_context_id;
} while (this->user_contexts.contains(actual_user_ctx_id));
this->user_contexts[actual_user_ctx_id] = new UserContext(this, actual_user_ctx_id, user_id);
return actual_user_ctx_id;
}
s32 LibraryContext::CreatePushEventHandle() {
std::scoped_lock lk{this->lock};
if (g_current_push_event_handle_id + 1 < 0xf0000000) {
++g_current_push_event_handle_id;
} else {
g_current_push_event_handle_id = 1;
}
s32 new_handle_id = g_current_push_event_handle_id;
this->push_event_handles[new_handle_id] = new PushEventHandle(new_handle_id);
return new_handle_id;
}
PushEventHandle* LibraryContext::GetPushEventHandle(s32 handle_id) {
std::scoped_lock lk{lock};
if (!push_event_handles.contains(handle_id)) {
return nullptr;
}
PushEventHandle* handle = push_event_handles[handle_id];
handle->AddUser();
return handle;
}
s32 LibraryContext::DeletePushEventHandle(PushEventHandle* handle) {
this->Lock();
s32 handle_id = handle->GetId();
if (handle->IsDeleted()) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "handle with id {:#x} is already deleted", handle_id);
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
handle->MarkForDeletion();
handle->Abort();
while (handle->IsBusy()) {
handle->RemoveUser();
this->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
this->Lock();
handle->AddUser();
}
if (push_event_handles.contains(handle_id)) {
push_event_handles.erase(handle_id);
}
delete handle;
return ORBIS_OK;
}
s32 LibraryContext::CreatePushEventFilter(
s32 handle_id, const char* np_service_name, OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param, u64 filter_param_num,
bool internal) {
std::scoped_lock lk{this->lock};
if (!this->push_event_handles.contains(handle_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event handle with id {:#x}", handle_id);
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
PushEventHandle* handle = this->push_event_handles[handle_id];
handle->AddUser();
if (g_current_push_event_filter_id + 1 < 0xf0000000) {
++g_current_push_event_filter_id;
} else {
g_current_push_event_filter_id = 1;
}
s32 new_filter_id = g_current_push_event_filter_id;
this->push_event_filters[new_filter_id] =
new PushEventFilter(new_filter_id, np_service_name, np_service_label, internal);
PushEventFilter* filter = this->push_event_filters[new_filter_id];
s32 result = filter->Initialize(handle, filter_param, filter_param_num);
if (result < 0) {
// Failed to initialize filter.
delete filter;
this->push_event_filters.erase(new_filter_id);
handle->RemoveUser();
return result;
}
handle->RemoveUser();
return new_filter_id;
}
UserContext* LibraryContext::GetUserContext(s32 user_ctx_id) {
std::scoped_lock lk{this->lock};
if (!this->user_contexts.contains(user_ctx_id)) {
return nullptr;
}
UserContext* user_ctx = this->user_contexts.at(user_ctx_id);
if (user_ctx->IsDeleted()) {
return nullptr;
}
user_ctx->AddUser();
return user_ctx;
}
UserContext* LibraryContext::GetUserContextByUserId(
Libraries::UserService::OrbisUserServiceUserId user_id) {
if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
return nullptr;
}
std::scoped_lock lk{this->lock};
for (auto& [user_ctx_id, user_ctx] : this->user_contexts) {
if (user_ctx->GetUserId() == user_id) {
user_ctx->AddUser();
return user_ctx;
}
}
return nullptr;
}
s32 PS4_SYSV_ABI internalPreSendCallback(s32 http_request_id, s32 ssl_id,
Libraries::Http2::OrbisHttp2PreSendCallbackData* data,
void* user_arg) {
s64 user_ctx_id = reinterpret_cast<s64>(user_arg);
LOG_ERROR(Lib_NpWebApi2,
"Unimplemented, http_request_id = {:#x}, ssl_id = {:#x}, user_ctx_id = {:#x}",
http_request_id, ssl_id, user_ctx_id);
return ORBIS_OK;
}
s32 UserContext::Initialize() {
const char* name = this->parent_ctx->GetName();
char sw_version[8];
std::memset(sw_version, 0, sizeof(sw_version));
Libraries::Np::NpCommon::sceNpGetSdkVersion(sw_version);
char user_agent_buf[0x40];
std::memset(user_agent_buf, 0, sizeof(user_agent_buf));
if (name) {
std::snprintf(user_agent_buf, sizeof(user_agent_buf), "NpWebApi2/%s (%s)", sw_version,
name);
} else {
std::snprintf(user_agent_buf, sizeof(user_agent_buf), "NpWebApi2/%s", sw_version);
}
this->user_agent = std::string{user_agent_buf};
s32 http_ctx_id = this->parent_ctx->GetHttpCtxId();
http_template_id = Libraries::Http2::sceHttp2CreateTemplate(
http_ctx_id, user_agent_buf,
Libraries::Http2::OrbisHttp2HttpVersion::ORBIS_HTTP2_VERSION_2_0, 0);
if (http_template_id < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to create HTTP template, error = {:#x}", http_template_id);
return http_template_id;
}
s32 result = Libraries::Http2::sceHttp2SetPreSendCallback(
http_template_id, internalPreSendCallback, reinterpret_cast<void*>(this->id));
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to set pre-send callback, error = {:#x}", result);
return result;
}
return result;
}
void UserContext::CheckTimeout() {
parent_ctx->Lock();
for (auto& [request_id, request] : requests) {
request->CheckTimeout();
}
parent_ctx->Unlock();
}
s32 UserContext::CreatePushEventCallback(s32 filter_id, OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg) {
this->Lock();
if (g_current_push_event_callback_id + 1 < 0xf0000000) {
++g_current_push_event_callback_id;
} else {
g_current_push_event_callback_id = 1;
}
s32 new_callback_id = g_current_push_event_callback_id;
this->push_event_callbacks[new_callback_id] =
new PushEventCallback(new_callback_id, filter_id, cb_func, user_arg, false);
this->Unlock();
return new_callback_id;
}
s32 UserContext::DeletePushEventCallback(s32 callback_id) {
this->Lock();
if (!this->push_event_callbacks.contains(callback_id)) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "No push event callback with id {:#x}", callback_id);
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
PushEventCallback* callback = this->push_event_callbacks[callback_id];
while (callback->is_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
delete callback;
this->push_event_callbacks.erase(callback_id);
this->Unlock();
return ORBIS_OK;
}
void UserContext::CreatePushContext(OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
this->Lock();
while (this->push_contexts_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(10000);
this->Lock();
}
this->push_contexts_busy = true;
PushEventPushContext* push_ctx = new PushEventPushContext(5000, this);
push_ctx->Initialize();
OrbisNpWebApi2PushEventPushContextId* new_id = push_ctx->GetId();
std::memcpy(push_ctx_id, new_id, sizeof(*push_ctx_id));
s64 raw_id = push_ctx->GetFakeId();
this->push_contexts[raw_id] = push_ctx;
this->push_contexts_busy = false;
this->Unlock();
}
PushEventPushContext* UserContext::GetPushContext(
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
parent_ctx->Lock();
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
if (!push_contexts.contains(raw_id)) {
return nullptr;
}
PushEventPushContext* push_ctx = push_contexts[raw_id];
parent_ctx->Unlock();
return push_ctx;
}
void UserContext::DeletePushContext(PushEventPushContext* push_ctx) {
this->Lock();
while (this->push_contexts_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(10000);
this->Lock();
}
this->push_contexts_busy = true;
while (push_ctx->CallbackRunning()) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
s64 raw_id = push_ctx->GetFakeId();
this->push_contexts.erase(raw_id);
delete push_ctx;
this->push_contexts_busy = false;
this->Unlock();
}
s32 UserContext::CreatePushContextCallback(s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
this->Lock();
if (g_current_push_context_callback_id + 1 < 0xf0000000) {
++g_current_push_context_callback_id;
} else {
g_current_push_context_callback_id = 1;
}
s32 new_callback_id = g_current_push_context_callback_id;
this->push_context_callbacks[new_callback_id] =
new PushEventPushContextCallback(new_callback_id, filter_id, cb_func, user_arg, false);
this->Unlock();
return new_callback_id;
}
s32 UserContext::DeletePushContextCallback(s32 callback_id) {
this->Lock();
if (!this->push_context_callbacks.contains(callback_id)) {
this->Unlock();
LOG_ERROR(Lib_NpWebApi2, "No push event push context callback with id {:#x}", callback_id);
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
PushEventPushContextCallback* callback = this->push_context_callbacks[callback_id];
while (callback->is_busy) {
this->Unlock();
Libraries::Kernel::sceKernelUsleep(100000);
this->Lock();
}
delete callback;
this->push_context_callbacks.erase(callback_id);
this->Unlock();
return ORBIS_OK;
}
s32 UserContext::CreateRequest(const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter,
bool multipart, Request** request) {
this->Lock();
s64 actual_request_id = 0;
do {
g_current_request_id++;
if (g_current_request_id >> 0x20 != 0) {
g_current_request_id = 1;
}
actual_request_id = (static_cast<s64>(this->id) << 0x20) | g_current_request_id;
} while (this->requests.contains(actual_request_id));
if (content_parameter) {
this->requests[actual_request_id] =
new Request(this->parent_ctx, actual_request_id, api_group, path, method, multipart,
content_parameter);
} else {
this->requests[actual_request_id] =
new Request(this->parent_ctx, actual_request_id, api_group, path, method, multipart);
}
*request = this->requests[actual_request_id];
this->Unlock();
return ORBIS_OK;
}
Request* UserContext::GetRequest(s64 request_id) {
this->Lock();
if (!this->requests.contains(request_id)) {
this->Unlock();
return nullptr;
}
Request* request = this->requests[request_id];
request->AddUser();
this->Unlock();
return request;
}
bool UserContext::HasBusyRequests() {
this->Lock();
for (auto& [request_id, request] : this->requests) {
request->AddUser();
bool busy = request->IsBusy();
request->RemoveUser();
if (busy) {
this->Unlock();
return true;
}
}
this->Unlock();
return false;
}
void UserContext::AbortAllRequests() {
this->Lock();
for (auto& [request_id, request] : this->requests) {
request->Abort();
}
this->Unlock();
}
void UserContext::Delete() {
if (this->http_template_id != 0) {
Libraries::Http2::sceHttp2DeleteTemplate(this->http_template_id);
}
this->parent_ctx->RemoveUserContext(this->id);
this->RemoveUser();
for (auto& [request_id, request] : this->requests) {
request->Delete(nullptr);
}
this->requests.clear();
for (auto& [push_ctx_id, push_ctx] : this->push_contexts) {
delete push_ctx;
}
this->push_contexts.clear();
for (auto& [callback_id, callback] : this->push_event_callbacks) {
delete callback;
}
this->push_event_callbacks.clear();
for (auto& [callback_id, callback] : this->push_context_callbacks) {
delete callback;
}
this->push_context_callbacks.clear();
delete this;
}
static bool IsInternalHeader(const char* header_name) {
std::string lower_name{header_name};
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
[](char c) { return std::tolower(c); });
// These headers are all skipped if the request's library context
// was not created through the IntInitialize functions.
return lower_name.compare("content-type") == 0 || lower_name.compare("user-agent") == 0 ||
lower_name.compare("authorization") == 0 ||
lower_name.compare("x-psn-np-title-id") == 0 ||
lower_name.compare("x-psn-np-title-token") == 0 ||
lower_name.compare("x-psn-request-id") == 0 ||
lower_name.compare("x-psn-sdk-ver") == 0 ||
lower_name.compare("x-psn-debug-settings") == 0 || lower_name.compare("npdebug") == 0 ||
lower_name.compare("x-psn-trc-check-notification-enabled") == 0 ||
lower_name.compare("x-psn-webtrace-enabled") == 0 ||
lower_name.compare("x-psn-npwebapi2-context") == 0 ||
lower_name.compare("x-psn-fake-rate-limit-enabled") == 0 ||
lower_name.compare("x-psn-fake-rate-limit-targets") == 0;
}
s32 Request::AddHttpRequestHeader(const char* field_name, const char* field_value) {
if (!this->parent_ctx->IsInternal() && IsInternalHeader(field_name)) {
LOG_WARNING(Lib_NpWebApi2, "This is not an internal context, skipping field name {}",
field_name);
return ORBIS_OK;
}
this->Lock();
this->http_headers.emplace_back(new HttpRequestHeader(field_name, field_value));
this->Unlock();
return ORBIS_OK;
}
s32 Request::CreateHttpRequest(s32 http_template_id, const char* url) {
const char* method = this->method.empty() ? "UNKNOWN" : this->method.data();
s32 http_request_id = Libraries::Http2::sceHttp2CreateRequestWithURL(http_template_id, method,
url, this->content_length);
if (http_request_id < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to create Http2 request, error = {:#x}", http_request_id);
return http_request_id;
}
this->http_request_id = http_request_id;
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Content-Type",
this->content_type.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to add Content-Type request header, error = {:#x}",
result);
return result;
}
UserContext* user_ctx = this->parent_ctx->GetUserContext(this->id >> 0x20);
const std::string bearer = NpHandler::GetInstance().GetBearerToken(user_ctx->GetUserId());
if (!bearer.empty()) {
const std::string auth_value = "Bearer " + bearer;
result = Libraries::Http2::sceHttp2AddRequestHeader(http_request_id, "Authorization",
auth_value.data(), 0);
}
if (bearer.empty() || result < 0) {
LOG_WARNING(Lib_NpWebApi2, "Failed to add Authorization request header");
}
// This would go on to add several other PSN-specific headers.
// As shadNet does not appear to use them, the logic is skipped for now.
for (HttpRequestHeader* header : this->http_headers) {
s32 result = Libraries::Http2::sceHttp2AddRequestHeader(
http_request_id, header->field_name.data(), header->field_value.data(), 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2,
"Failed to add request header, name = {}, value = {}, error = {:#x}",
header->field_name.data(), header->field_value.data(), result);
user_ctx->RemoveUser();
return result;
}
}
user_ctx->RemoveUser();
return result;
}
s32 Request::SendHttpRequest(void* data, u64 data_size) {
s32 result = 0;
if (this->content_length != 0 && this->sent_data == 0) {
// Real library seems to do some calculations, some parts including IPC calls.
// Not entirely sure how accurate this is in practice.
result = Libraries::Http2::sceHttp2SetRequestContentLength(this->http_request_id,
this->content_length);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to set content length, error = {:#x}", result);
return result;
}
}
if (!data || data_size == 0 || this->content_length == 0) {
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, nullptr, 0);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
}
u64 actual_size = std::min<u64>(data_size, this->content_length - this->sent_data);
if (this->content_length <= this->sent_data) {
// Nothing left to send
return result;
}
result = Libraries::Http2::sceHttp2SendRequest(this->http_request_id, data, actual_size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to send request, error = {:#x}", result);
return result;
}
this->sent_data += actual_size;
return result;
}
s32 Request::GetAllHttpResponseHeaders() {
char* header = nullptr;
u64 size = 0;
s32 result =
Libraries::Http2::sceHttp2GetAllResponseHeaders(this->http_request_id, &header, &size);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to get response headers, error = {:#x}", result);
return result;
}
this->http_response_headers = header;
this->http_response_header_size = size;
return result;
}
s32 Request::ParseHttpResponseHeaders(const char* field_name, char* value, u64 value_size,
u64* value_size_out) {
const char* temp_val{};
u64 temp_size{};
s32 result = Libraries::Http::sceHttpParseResponseHeader(this->http_response_headers,
this->http_response_header_size,
field_name, &temp_val, &temp_size);
if (result >= 0) {
if (value) {
std::memset(value, 0, value_size);
u64 size = std::min<u64>(value_size, temp_size);
std::memcpy(value, temp_val, size);
}
if (value_size_out) {
*value_size_out = temp_size;
}
result = ORBIS_OK;
}
return result;
}
s32 Request::Abort() {
this->Lock();
s32 result = 0;
if (this->Aborted()) {
// Nothing to do.
this->Unlock();
return result;
}
this->aborted = true;
// Real library has multiple states, we don't since our logic is simpler.
if (this->send_state == 3) {
// In the middle of an Http request, that needs aborting.
result = Libraries::Http2::sceHttp2AbortRequest(this->http_request_id);
if (result < 0) {
LOG_ERROR(Lib_NpWebApi2, "Failed to abort http request, error = {:#x}", result);
}
}
this->Unlock();
return result;
}
s32 Request::Delete(s32* http_request) {
for (HttpRequestHeader* header : this->http_headers) {
delete header;
}
this->http_headers.clear();
if (!http_request && this->http_request_id) {
Libraries::Http2::sceHttp2DeleteRequest(this->http_request_id);
} else {
*http_request = this->http_request_id;
}
while (this->user_count > 0) {
Libraries::Kernel::sceKernelUsleep(50000);
}
delete this;
return ORBIS_OK;
}
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,463 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/kernel/time.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/np/np_web_api2/np_web_api2_push_event.h"
#include "core/libraries/system/userservice.h"
#include <atomic>
#include <map>
#include <mutex>
#include <string>
#include <vector>
namespace Libraries::Np::NpWebApi2 {
class UserContext;
class Request;
class LibraryContext {
public:
LibraryContext(s32 ctx_id, s32 type, s32 http_ctx_id, u64 pool_size)
: id(ctx_id), type(static_cast<LibraryContextType>(type)), http_ctx_id(http_ctx_id),
pool_size(pool_size) {}
LibraryContext(s32 ctx_id, s32 type, s32 http_ctx_id, u64 pool_size, const char* name)
: id(ctx_id), type(static_cast<LibraryContextType>(type)), http_ctx_id(http_ctx_id),
pool_size(pool_size), name(name) {}
void Lock() {
lock.lock();
}
void Unlock() {
lock.unlock();
}
void AddUser() {
std::scoped_lock lk{lock};
user_count++;
}
void RemoveUser() {
std::scoped_lock lk{lock};
user_count--;
}
s32 GetId() {
return id;
}
s32 GetHttpCtxId() {
return http_ctx_id;
}
u64 GetPoolSize() {
return pool_size;
}
const char* GetName() {
return name.empty() ? nullptr : name.data();
}
bool IsDebug() {
return type == LibraryContextType::Debug;
}
bool IsNormal() {
return type == LibraryContextType::Normal;
}
bool IsInternal() {
return type == LibraryContextType::Internal;
}
bool IsPresence() {
return type == LibraryContextType::Presence;
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
std::scoped_lock lk{lock};
return user_count > 1;
}
void CheckTimeout();
s32 CreatePushEventHandle();
PushEventHandle* GetPushEventHandle(s32 handle_id);
void SetHandleEndTime(s32 handle_id) {
std::scoped_lock lk{lock};
if (push_event_handles.contains(handle_id)) {
auto& handle = push_event_handles[handle_id];
handle->SetEndTime();
}
}
void AbortAllPushEventHandles() {
std::scoped_lock lk{lock};
for (auto& [handle_id, handle] : push_event_handles) {
abortPushEventHandle(id, handle_id);
}
}
bool HasBusyPushEventHandles() {
std::scoped_lock lk{lock};
for (auto& [handle_id, handle] : push_event_handles) {
if (handle->IsBusy()) {
return true;
}
}
return false;
}
s32 DeletePushEventHandle(PushEventHandle* handle);
void DeleteAllPushEventHandles() {
for (auto& [handle_id, handle] : push_event_handles) {
delete handle;
}
push_event_handles.clear();
}
s32 CreatePushEventFilter(s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal);
PushEventFilter* GetPushEventFilter(s32 filter_id) {
std::scoped_lock lk{lock};
if (!push_event_filters.contains(filter_id)) {
return nullptr;
}
return push_event_filters[filter_id];
}
void DeletePushEventFilter(PushEventFilter* filter) {
std::scoped_lock lk{lock};
s32 filter_id = filter->GetId();
if (push_event_filters.contains(filter_id)) {
push_event_filters.erase(filter_id);
}
delete filter;
}
void DeleteAllPushEventFilters() {
for (auto& [filter_id, filter] : push_event_filters) {
delete filter;
}
push_event_filters.clear();
}
s32 CreateUserContext(Libraries::UserService::OrbisUserServiceUserId user_id);
UserContext* GetUserContext(s32 user_ctx_id);
UserContext* GetUserContextByUserId(Libraries::UserService::OrbisUserServiceUserId user_id);
void RemoveUserContext(s32 user_ctx_id) {
std::scoped_lock lk{lock};
if (user_contexts.contains(user_ctx_id)) {
user_contexts.erase(user_ctx_id);
}
}
void DeleteAllUserContexts() {
std::scoped_lock lk{lock};
for (auto& [user_ctx_id, user_ctx] : user_contexts) {
deleteUserContext(user_ctx_id);
}
user_contexts.clear();
}
private:
s32 id{};
s32 http_ctx_id{};
s32 user_count{};
enum LibraryContextType : s32 {
Debug = 0,
Normal = 1,
Internal = 2,
Presence = 3,
} type;
u64 pool_size{};
bool deleting{};
std::recursive_mutex lock{};
std::string name{};
std::map<s32, UserContext*> user_contexts{};
std::map<s32, PushEventHandle*> push_event_handles{};
std::map<s32, PushEventFilter*> push_event_filters{};
};
class UserContext {
public:
UserContext(LibraryContext* parent, s32 user_ctx_id,
Libraries::UserService::OrbisUserServiceUserId user_id)
: parent_ctx(parent), id(user_ctx_id), user_id(user_id) {}
void Lock() {
parent_ctx->Lock();
}
void Unlock() {
parent_ctx->Unlock();
}
void AddUser() {
Lock();
user_count++;
Unlock();
}
void RemoveUser() {
Lock();
user_count--;
Unlock();
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
parent_ctx->Lock();
s32 users = user_count;
parent_ctx->Unlock();
// Assumes caller is a user
return users > 1;
}
s32 GetId() {
return id;
}
Libraries::UserService::OrbisUserServiceUserId GetUserId() {
return user_id;
}
s32 GetHttpTemplateId() {
return http_template_id;
}
s32 Initialize();
void CheckTimeout();
s32 CreatePushEventCallback(s32 filter_id, OrbisNpWebApi2PushEventCallback cb_func,
void* user_arg);
s32 DeletePushEventCallback(s32 callback_id);
void CreatePushContext(OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
PushEventPushContext* GetPushContext(const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
void DeletePushContext(PushEventPushContext* push_ctx_id);
s32 CreatePushContextCallback(s32 filter_id, OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg);
s32 DeletePushContextCallback(s32 callback_id);
s32 CreateRequest(const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
Request** request);
Request* GetRequest(s64 request_id);
bool HasBusyRequests();
void AbortAllRequests();
void RemoveRequest(s64 request_id) {
parent_ctx->Lock();
if (requests.contains(request_id)) {
requests.erase(request_id);
}
parent_ctx->Unlock();
}
void Delete();
private:
s32 id{};
Libraries::UserService::OrbisUserServiceUserId user_id{};
s32 user_count{};
s32 http_template_id{};
bool deleting{};
bool push_contexts_busy{};
LibraryContext* parent_ctx{};
std::string user_agent{};
std::map<s64, Request*> requests{};
std::map<s32, PushEventCallback*> push_event_callbacks{};
std::map<s64, PushEventPushContext*> push_contexts{};
std::map<s32, PushEventPushContextCallback*> push_context_callbacks{};
};
class Request {
public:
Request(LibraryContext* parent, s64 request_id, const char* api_group, const char* path,
const char* method, bool multipart)
: parent_ctx(parent), id(request_id), api_group(api_group), path(path), method(method),
multipart_supported(multipart) {}
Request(LibraryContext* parent, s64 request_id, const char* api_group, const char* path,
const char* method, bool multipart,
const OrbisNpWebApi2ContentParameter* content_parameter)
: parent_ctx(parent), id(request_id), api_group(api_group), path(path), method(method),
multipart_supported(multipart), content_type(content_parameter->content_type),
content_length(content_parameter->content_length) {}
void Lock() {
parent_ctx->Lock();
}
void Unlock() {
parent_ctx->Unlock();
}
void AddUser() {
Lock();
user_count++;
Unlock();
}
void RemoveUser() {
Lock();
user_count--;
Unlock();
}
s64 GetId() {
return id;
}
s32 GetHttpRequestId() {
return http_request_id;
}
bool IsMultipart() {
return multipart_supported;
}
bool HasSent() {
return sent;
}
void MarkSent() {
sent = true;
}
bool Aborted() {
return aborted;
}
bool Expired() {
return expired;
}
bool OutOfData() {
return content_length == sent_data;
}
void SetEndTime() {
if (timeout != 0 && end_time == 0) {
end_time = Libraries::Kernel::sceKernelGetProcessTime() + timeout;
}
}
void ClearEndTime() {
end_time = 0;
}
std::string& GetApiGroup() {
return api_group;
}
std::string& GetPath() {
return path;
}
std::string& GetMethod() {
return method;
}
std::string& GetContentType() {
return content_type;
}
void SetState(s32 state) {
send_state = state;
}
bool IsDeleted() {
return deleting;
}
void MarkForDeletion() {
deleting = true;
}
bool IsBusy() {
parent_ctx->Lock();
s32 users = user_count;
parent_ctx->Unlock();
// Assumes caller is a user
return users > 1;
}
s32 AddHttpRequestHeader(const char* field_name, const char* field_value);
void SetTimeout(u32 new_timeout) {
this->timeout = new_timeout;
}
void CheckTimeout() {
u64 time = Libraries::Kernel::sceKernelGetProcessTime();
if (!expired && end_time != 0 && end_time < time) {
expired = true;
Abort();
}
}
s32 CreateHttpRequest(s32 http_template_id, const char* url);
s32 SendHttpRequest(void* data, u64 data_size);
s32 GetAllHttpResponseHeaders();
s32 ParseHttpResponseHeaders(const char* field_name, char* value, u64 value_size,
u64* value_size_out);
s32 Abort();
s32 Delete(s32* http_request_id);
private:
s64 id{};
u64 content_length{};
char* http_response_headers{};
u64 http_response_header_size{};
s32 user_count{};
s32 http_request_id{};
u32 timeout{};
s32 sent_data{};
s32 send_state{};
bool multipart_supported{};
bool sent{};
bool aborted{};
bool expired{};
bool deleting{};
u64 end_time{};
LibraryContext* parent_ctx{};
std::string api_group{};
std::string path{};
std::string method{};
std::string content_type{};
struct HttpRequestHeader {
std::string field_name;
std::string field_value;
};
std::vector<HttpRequestHeader*> http_headers{};
};
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,932 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
#include "core/libraries/np/np_web_api2/np_web_api2_internal.h"
#include "core/libraries/system/userservice.h"
#include <map>
#include <mutex>
namespace Libraries::Np::NpWebApi2 {
std::recursive_mutex g_mutex{};
s32 g_current_lib_context_id{};
std::map<s32, LibraryContext*> g_lib_contexts{};
u64 g_last_timeout_check{};
s32 createLibraryContext(s32 http_ctx_id, s32 type, u64 pool_size, const char* name) {
std::scoped_lock lk{g_mutex};
if (g_lib_contexts.size() >= 0x8000) {
LOG_ERROR(Lib_NpWebApi2, "Too many library contexts");
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_MAX;
}
do {
g_current_lib_context_id++;
if (g_current_lib_context_id >= 0x8000) {
g_current_lib_context_id = 1;
}
} while (g_lib_contexts.contains(g_current_lib_context_id));
if (!name) {
g_lib_contexts[g_current_lib_context_id] =
new LibraryContext(g_current_lib_context_id, type, http_ctx_id, pool_size);
} else {
g_lib_contexts[g_current_lib_context_id] =
new LibraryContext(g_current_lib_context_id, type, http_ctx_id, pool_size, name);
}
return g_current_lib_context_id;
}
LibraryContext* getLibraryContext(s32 lib_ctx_id) {
std::scoped_lock lk{g_mutex};
if (!g_lib_contexts.contains(lib_ctx_id)) {
return nullptr;
}
LibraryContext* lib_ctx = g_lib_contexts.at(lib_ctx_id);
if (lib_ctx->IsDeleted()) {
return nullptr;
}
lib_ctx->AddUser();
return lib_ctx;
}
s32 getMemoryPoolStats(s32 lib_ctx_id, OrbisNpWebApi2MemoryPoolStats* stats) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
if (stats) {
memset(stats, 0, sizeof(*stats));
stats->pool_size = lib_ctx->GetPoolSize();
}
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 createPushEventHandle(s32 lib_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
s32 result = lib_ctx->CreatePushEventHandle();
lib_ctx->RemoveUser();
return result;
}
s32 setHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
lib_ctx->Lock();
handle->SetTimeout(timeout);
handle->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 abortPushEventHandle(s32 lib_ctx_id, s32 handle_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
lib_ctx->Lock();
handle->Abort();
lib_ctx->Unlock();
handle->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 deletePushEventHandle(s32 lib_ctx_id, s32 handle_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventHandle* handle = lib_ctx->GetPushEventHandle(handle_id);
if (!handle) {
LOG_ERROR(Lib_NpWebApi2, "No handle with id {:#x}", handle_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
s32 result = lib_ctx->DeletePushEventHandle(handle);
lib_ctx->RemoveUser();
return result;
}
s32 createPushEventFilter(s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->SetHandleEndTime(handle_id);
s32 result = lib_ctx->CreatePushEventFilter(handle_id, np_service_name, np_service_label,
filter_param, filter_param_num, internal);
lib_ctx->RemoveUser();
return result;
}
s32 deletePushEventFilter(s32 lib_ctx_id, s32 filter_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
PushEventFilter* filter = lib_ctx->GetPushEventFilter(filter_id);
if (!filter) {
LOG_ERROR(Lib_NpWebApi2, "No filter with id {:#x}", filter_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
lib_ctx->DeletePushEventFilter(filter);
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 terminateLibraryContext(s32 lib_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
lib_ctx->DeleteAllUserContexts();
lib_ctx->AbortAllPushEventHandles();
if (lib_ctx->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "Library context with id {:#x} is already deleted", lib_ctx_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->MarkForDeletion();
while (lib_ctx->IsBusy() || lib_ctx->HasBusyPushEventHandles()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
lib_ctx->Unlock();
lib_ctx->RemoveUser();
g_mutex.lock();
g_lib_contexts.erase(lib_ctx_id);
g_mutex.unlock();
lib_ctx->DeleteAllPushEventHandles();
lib_ctx->DeleteAllPushEventFilters();
delete lib_ctx;
return ORBIS_OK;
}
s32 createUserContext(s32 lib_ctx_id, Libraries::UserService::OrbisUserServiceUserId user_id) {
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context with id {:#x}", lib_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContextByUserId(user_id);
if (user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "User context already exists for user id {}", user_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_ALREADY_EXIST;
}
s32 user_ctx_id = lib_ctx->CreateUserContext(user_id);
if (user_ctx_id < 0) {
// user_ctx_id contains an error code, return it.
lib_ctx->RemoveUser();
return user_ctx_id;
}
user_ctx = lib_ctx->GetUserContext(user_ctx_id);
user_ctx->RemoveUser();
s32 result = user_ctx->Initialize();
if (result < 0) {
// Failed to initialize user_ctx, destroy it.
lib_ctx->RemoveUserContext(user_ctx_id);
delete user_ctx;
}
lib_ctx->RemoveUser();
return user_ctx_id;
}
s32 deleteUserContext(s32 user_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->AbortAllRequests();
if (user_ctx->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "User context with id {:#x} is already deleted", user_ctx_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->MarkForDeletion();
while (user_ctx->IsBusy() || user_ctx->HasBusyRequests()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
user_ctx->Delete();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 registerPushEventCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func, void* user_arg) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (!lib_ctx->GetPushEventFilter(filter_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event filter with id {:#x}", filter_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
s32 result = user_ctx->CreatePushEventCallback(filter_id, cb_func, user_arg);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 unregisterPushEventCallback(s32 user_ctx_id, s32 callback_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
s32 result = user_ctx->DeletePushEventCallback(callback_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 createPushContext(s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
user_ctx->CreatePushContext(push_ctx_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 startPushContextCallback(s32 user_ctx_id,
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
PushEventPushContext* push_ctx = user_ctx->GetPushContext(push_ctx_id);
if (!push_ctx) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_ERROR(Lib_NpWebApi2, "No push context with id {}", raw_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_CONTEXT_NOT_FOUND;
}
push_ctx->Start();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 deletePushContext(s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
PushEventPushContext* push_ctx = user_ctx->GetPushContext(push_ctx_id);
if (!push_ctx) {
s64 raw_id{};
std::memcpy(&raw_id, push_ctx_id, sizeof(s64));
LOG_ERROR(Lib_NpWebApi2, "No push context with id {}", raw_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_CONTEXT_NOT_FOUND;
}
user_ctx->DeletePushContext(push_ctx);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
s32 registerPushContextCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func,
void* user_arg) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (!lib_ctx->GetPushEventFilter(filter_id)) {
LOG_ERROR(Lib_NpWebApi2, "No push event filter with id {:#x}", filter_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
s32 result = user_ctx->CreatePushContextCallback(filter_id, cb_func, user_arg);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 unregisterPushContextCallback(s32 user_ctx_id, s32 callback_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
s32 result = user_ctx->DeletePushContextCallback(callback_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
void processPushEvents() {
// LOG_ERROR(Lib_NpWebApi2, "(STUBBED)");
}
s32 createRequest(s32 user_ctx_id, const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
s64* request_id) {
LibraryContext* lib_ctx = getLibraryContext(user_ctx_id >> 0x10);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for user context id {:#x}", user_ctx_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context with id {:#x}", user_ctx_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
if (std::strlen(api_group) >= 63) {
LOG_ERROR(Lib_NpWebApi2, "API group is too long");
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PARAMETER_TOO_LONG;
}
Request* request{};
s32 result =
user_ctx->CreateRequest(api_group, path, method, content_parameter, multipart, &request);
if (result < 0) {
// Request creation failed
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
if (request) {
delete request;
}
return result;
}
if (request_id) {
*request_id = request->GetId();
}
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* field_value) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
if (request->HasSent()) {
LOG_ERROR(Lib_NpWebApi2, "Request is already sent");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_AFTER_SEND;
}
s32 result = request->AddHttpRequestHeader(field_name, field_value);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 setRequestTimeout(s64 request_id, u32 timeout) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetTimeout(timeout);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_OK;
}
static s32 checkRequestStatus(Request* request, UserContext* user_ctx, LibraryContext* lib_ctx) {
if (request->Expired()) {
LOG_ERROR(Lib_NpWebApi2, "Request timed out");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_TIMEOUT;
}
if (request->Aborted()) {
LOG_ERROR(Lib_NpWebApi2, "Request aborted");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_ABORTED;
}
return ORBIS_OK;
}
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetEndTime();
if (request->IsMultipart()) {
if (part_index == 0) {
LOG_ERROR(Lib_NpWebApi2, "part_index 0 is prohibited for multipart requests");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_PROHIBITED_FUNCTION_CALL;
}
// TODO: Multipart logic
}
if (!request->HasSent()) {
request->MarkSent();
}
request->Lock();
request->SetState(0);
s32 result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
if (!EmulatorSettings.IsShadNetEnabled()) {
LOG_INFO(Lib_NpWebApi2, "Cannot send request, you are not signed in to shadNet");
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN;
}
if (request->GetHttpRequestId() == 0) {
std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
s32 template_id = user_ctx->GetHttpTemplateId();
// Technically this should be base_url + api_group + path,
// but shadNet doesn't seem to use the api_group for endpoints?
std::string full_url = base_url + request->GetPath();
result = request->CreateHttpRequest(template_id, full_url.data());
if (result < 0) {
// Underlying request creation failed, return.
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
}
request->Lock();
request->SetState(3);
result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
if (request->IsMultipart()) {
// TODO: Multipart sends
// result = request->SendMultipartHttpRequest(data, data_size, part_index);
} else {
result = request->SendHttpRequest(data, data_size);
}
if (result >= 0 && request->OutOfData()) {
result = request->GetAllHttpResponseHeaders();
if (result >= 0) {
s32 http_request_id = request->GetHttpRequestId();
s32 http_status{};
result = Libraries::Http2::sceHttp2GetStatusCode(http_request_id, &http_status);
if (result >= 0) {
if (resp_info_option) {
resp_info_option->http_status = http_status;
}
if (http_status >= 400) {
result = http_status | 0x82f00000;
}
} else {
LOG_ERROR(Lib_NpWebApi2, "Failed to get http status code, error = {:#x}", result);
}
}
}
request->Lock();
request->SetState(0);
s32 result2 = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result2 != 0) {
// Aborted or timed out.
return result2;
}
request->ClearEndTime();
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 getHttpResponseHeaderData(s64 request_id, const char* field_name, char* value, u64 value_size,
u64* value_size_out) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
s32 result = request->ParseHttpResponseHeaders(field_name, value, value_size, value_size_out);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 readData(s64 request_id, void* data, u64 size) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->SetEndTime();
request->Lock();
request->SetState(3);
s32 result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
s32 http_req_id = request->GetHttpRequestId();
s32 result2 = Libraries::Http2::sceHttp2ReadData(http_req_id, data, size);
request->Lock();
request->SetState(0);
result = checkRequestStatus(request, user_ctx, lib_ctx);
request->Unlock();
if (result != 0) {
// Aborted or timed out.
return result;
}
request->ClearEndTime();
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result2;
}
s32 abortRequest(s64 request_id) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->RemoveUser();
s32 result = request->Abort();
user_ctx->RemoveUser();
lib_ctx->RemoveUser();
return result;
}
s32 deleteRequest(s64 request_id) {
s32 lib_ctx_id = static_cast<s32>(request_id >> 0x30);
s32 user_ctx_id = static_cast<s32>(request_id >> 0x20);
LibraryContext* lib_ctx = getLibraryContext(lib_ctx_id);
if (!lib_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No library context for request id {:#x}", request_id);
return ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND;
}
lib_ctx->Lock();
UserContext* user_ctx = lib_ctx->GetUserContext(user_ctx_id);
if (!user_ctx) {
LOG_ERROR(Lib_NpWebApi2, "No user context for request id {:#x}", request_id);
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND;
}
Request* request = user_ctx->GetRequest(request_id);
if (!request) {
LOG_ERROR(Lib_NpWebApi2, "No request with id {:#x}", request_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
if (request->IsDeleted()) {
// Already marked for deletion
LOG_ERROR(Lib_NpWebApi2, "Request with id {:#x} is already being deleted", request_id);
request->RemoveUser();
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
return ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND;
}
request->MarkForDeletion();
request->Abort();
while (request->IsBusy()) {
lib_ctx->Unlock();
Libraries::Kernel::sceKernelUsleep(50000);
lib_ctx->Lock();
}
user_ctx->RemoveRequest(request_id);
request->RemoveUser();
s32 http_request_id = 0;
request->Delete(&http_request_id);
user_ctx->RemoveUser();
lib_ctx->Unlock();
lib_ctx->RemoveUser();
if (http_request_id != 0) {
Libraries::Http2::sceHttp2DeleteRequest(http_request_id);
}
return ORBIS_OK;
}
void checkTimeout() {
u64 current_time = Libraries::Kernel::sceKernelGetProcessTime();
if (current_time < g_last_timeout_check + 1000) {
// Too soon, skip check.
return;
}
std::scoped_lock lk{g_mutex};
g_last_timeout_check = current_time;
for (auto& [lib_ctx_id, lib_ctx] : g_lib_contexts) {
lib_ctx->CheckTimeout();
}
}
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/np/np_web_api2/np_web_api2.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpWebApi2 {
s32 createLibraryContext(s32 http_ctx_id, s32 type, u64 pool_size, const char* name);
s32 getMemoryPoolStats(s32 lib_ctx_id, OrbisNpWebApi2MemoryPoolStats* stats);
s32 createPushEventHandle(s32 lib_ctx_id);
s32 setHandleTimeout(s32 lib_ctx_id, s32 handle_id, u32 timeout);
s32 abortPushEventHandle(s32 lib_ctx_id, s32 handle_id);
s32 deletePushEventHandle(s32 lib_ctx_id, s32 handle_id);
s32 createPushEventFilter(s32 lib_ctx_id, s32 handle_id, const char* np_service_name,
OrbisNpServiceLabel np_service_label,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num, bool internal);
s32 deletePushEventFilter(s32 lib_ctx_id, s32 filter_id);
s32 terminateLibraryContext(s32 lib_ctx_id);
s32 createUserContext(s32 lib_ctx_id, Libraries::UserService::OrbisUserServiceUserId user_id);
s32 deleteUserContext(s32 user_ctx_id);
s32 registerPushEventCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventCallback cb_func, void* user_arg);
s32 unregisterPushEventCallback(s32 user_ctx_id, s32 callback_id);
s32 createPushContext(s32 user_ctx_id, OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 startPushContextCallback(s32 user_ctx_id,
const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 deletePushContext(s32 user_ctx_id, const OrbisNpWebApi2PushEventPushContextId* push_ctx_id);
s32 registerPushContextCallback(s32 user_ctx_id, s32 filter_id,
OrbisNpWebApi2PushEventPushContextCallback cb_func, void* user_arg);
s32 unregisterPushContextCallback(s32 user_ctx_id, s32 callback_id);
void processPushEvents();
s32 createRequest(s32 user_ctx_id, const char* api_group, const char* path, const char* method,
const OrbisNpWebApi2ContentParameter* content_parameter, bool multipart,
s64* request_id);
s32 addHttpRequestHeader(s64 request_id, const char* field_name, const char* field_value);
s32 setRequestTimeout(s64 request_id, u32 timeout);
s32 sendRequest(s64 request_id, s32 part_index, void* data, u64 data_size,
OrbisNpWebApi2ResponseInformationOption* resp_info_option);
s32 getHttpResponseHeaderData(s64 request_id, const char* field_name, char* value, u64 value_size,
u64* value_size_out);
s32 readData(s64 request_id, void* data, u64 size);
s32 abortRequest(s64 request_id);
s32 deleteRequest(s64 request_id);
void checkTimeout();
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_web_api2/np_web_api2_push_event.h"
namespace Libraries::Np::NpWebApi2 {
s64 g_current_push_event_push_context_id{};
s32 PushEventFilter::Initialize(PushEventHandle* handle,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num) {
if (handle->IsDeleted()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} is invalid", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_HANDLE_NOT_FOUND;
}
if (handle->IsTimedout()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} has timed out", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_TIMEOUT;
}
if (handle->IsAborted()) {
LOG_ERROR(Lib_NpWebApi2, "handle {:#x} was aborted", handle->GetId());
return ORBIS_NP_WEBAPI2_ERROR_ABORTED;
}
std::vector<std::vector<OrbisNpWebApi2PushEventExtdDataKey>> copy_storage{};
for (u64 i = 0; i < filter_param_num; i++) {
OrbisNpWebApi2PushEventFilterParameter new_param{};
memcpy(&new_param, &filter_param[i], sizeof(OrbisNpWebApi2PushEventFilterParameter));
if (filter_param[i].extd_data_key != nullptr && filter_param[i].extd_data_key_num != 0) {
std::vector<OrbisNpWebApi2PushEventExtdDataKey> data_keys{};
for (u64 j = 0; j < filter_param[i].extd_data_key_num; j++) {
OrbisNpWebApi2PushEventExtdDataKey new_key{};
memcpy(&new_key, &filter_param[i].extd_data_key[j],
sizeof(OrbisNpWebApi2PushEventExtdDataKey));
data_keys.emplace_back(new_key);
}
new_param.extd_data_key = data_keys.data();
copy_storage.emplace_back(data_keys);
}
this->filter_params.emplace_back(new_param);
}
return ORBIS_OK;
}
void PushEventPushContext::Initialize() {
// In the real library, there's a whole process of registering this with NpManager's Push2 API.
// For now, we'll just create the uuid for this push context.
// As for the ID itself, for now we'll cheat a bit.
// Real library does a mostly random string, but it's easier to just use an actual index.
++g_current_push_event_push_context_id;
if (g_current_push_event_push_context_id < 0) {
g_current_push_event_push_context_id = 1;
}
s64 new_id = g_current_push_event_push_context_id;
std::memset(&this->id, 0, sizeof(this->id));
std::memcpy(&this->id, &new_id, sizeof(s64));
}
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_web_api2/np_web_api2.h"
namespace Libraries::Np::NpWebApi2 {
class UserContext;
class PushEventHandle {
public:
PushEventHandle(s32 new_id) : id(new_id) {}
s32 GetId() {
return id;
}
void SetTimeout(u32 new_timeout) {
timeout = new_timeout;
}
void AddUser() {
user_count++;
}
void RemoveUser() {
user_count--;
}
void Abort() {
aborted = true;
}
bool IsAborted() {
return aborted;
}
void MarkForDeletion() {
deleting = true;
}
bool IsDeleted() {
return deleting;
}
void SetEndTime() {
if (timeout != 0) {
end_time = Libraries::Kernel::sceKernelGetProcessTime() + timeout;
}
}
void ClearEndTime() {
end_time = 0;
}
u64 GetEndTime() {
return end_time;
}
bool IsTimedout() {
return timed_out;
}
void CheckTimeout(u64 time) {
if (!timed_out && end_time != 0 && end_time < time) {
timed_out = true;
Abort();
}
}
bool IsBusy() {
return user_count > 1;
}
private:
s32 id{};
u32 timeout{};
s32 user_count{};
bool aborted{};
bool deleting{};
bool timed_out{};
u64 end_time{};
};
class PushEventFilter {
public:
PushEventFilter(s32 new_id, const char* service_name, OrbisNpServiceLabel service_label,
bool is_internal)
: id(new_id), np_service_name(service_name ? service_name : ""),
np_service_label(service_label), internal(is_internal) {}
s32 Initialize(PushEventHandle* handle,
const OrbisNpWebApi2PushEventFilterParameter* filter_param,
u64 filter_param_num);
s32 GetId() {
return id;
}
private:
s32 id{};
OrbisNpServiceLabel np_service_label{};
bool internal{};
std::vector<OrbisNpWebApi2PushEventFilterParameter> filter_params{};
std::vector<std::vector<OrbisNpWebApi2PushEventExtdDataKey>> data_key_copy_storage{};
std::string np_service_name{};
};
struct PushEventCallback {
s32 id;
s32 filter_id;
OrbisNpWebApi2PushEventCallback cb_func;
void* user_arg;
bool is_busy;
};
class PushEventPushContext {
public:
PushEventPushContext(u32 new_timeout, UserContext* user_ctx)
: timeout(new_timeout), parent_user_ctx(user_ctx) {}
void Initialize();
OrbisNpWebApi2PushEventPushContextId* GetId() {
return &id;
}
// Returns the actual numerical id currently stored in id.
s64 GetFakeId() {
s64 fake_id{};
std::memcpy(&fake_id, &id, sizeof(s64));
return fake_id;
}
void Start() {
started = true;
}
void SetState(s32 new_state) {
state = new_state;
}
bool CallbackRunning() {
return state == 2;
}
private:
s32 state{};
u32 timeout{};
UserContext* parent_user_ctx{};
u64 end_time{};
u64 start_time{};
OrbisNpWebApi2PushEventPushContextId id{};
bool started{};
};
struct PushEventPushContextCallback {
s32 id;
s32 filter_id;
OrbisNpWebApi2PushEventPushContextCallback cb_func;
void* user_arg;
bool is_busy;
};
}; // namespace Libraries::Np::NpWebApi2
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <mutex>
#include <ranges>
template <typename T, size_t N, int INVALID_OBJECT_ID_ERROR, int OBJECT_NOT_FOUND_ERROR,
int MAX_OBJECTS_ERROR>
struct ObjectManager {
s32 GetObject(int objectId, T** out) {
std::scoped_lock lk{mutex};
if (objectId < 1 || objectId > N) {
return INVALID_OBJECT_ID_ERROR;
}
auto obj = objects[objectId - 1];
if (!obj) {
return OBJECT_NOT_FOUND_ERROR;
}
*out = obj;
return ORBIS_OK;
}
template <typename... Args>
s32 CreateObject(Args&&... args) {
std::scoped_lock lk{mutex};
if (auto slot = std::ranges::find(objects, nullptr); slot != objects.end()) {
*slot = new T{args...};
return std::ranges::distance(objects.begin(), slot) + 1;
}
return MAX_OBJECTS_ERROR;
}
s32 DeleteObject(int objectId) {
std::scoped_lock lk{mutex};
if (objectId < 1 || objectId > N) {
return INVALID_OBJECT_ID_ERROR;
}
auto obj = objects[objectId - 1];
if (!obj) {
return OBJECT_NOT_FOUND_ERROR;
}
delete obj;
objects[objectId - 1] = nullptr;
return ORBIS_OK;
}
private:
std::mutex mutex;
std::array<T*, N> objects = {nullptr};
};
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more