diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b78b13c0..f720c3e32 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,7 +202,7 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
-set(EMULATOR_VERSION_MINOR "13")
+set(EMULATOR_VERSION_MINOR "14")
set(EMULATOR_VERSION_PATCH "1")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
@@ -221,6 +221,7 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(Boost 1.84.0 CONFIG)
+find_package(CLI11 2.6.1 CONFIG)
find_package(FFmpeg 5.1.2 MODULE)
find_package(fmt 10.2.0 CONFIG)
find_package(glslang 15 CONFIG)
@@ -281,15 +282,16 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
src/core/libraries/audio/audioin.h
- src/core/libraries/audio/sdl_in.h
- src/core/libraries/audio/sdl_in.cpp
+ src/core/libraries/audio/audioin_backend.h
+ src/core/libraries/audio/audioin_error.h
+ src/core/libraries/audio/sdl_audio_in.cpp
src/core/libraries/voice/voice.cpp
src/core/libraries/voice/voice.h
src/core/libraries/audio/audioout.cpp
src/core/libraries/audio/audioout.h
src/core/libraries/audio/audioout_backend.h
src/core/libraries/audio/audioout_error.h
- src/core/libraries/audio/sdl_audio.cpp
+ src/core/libraries/audio/sdl_audio_out.cpp
src/core/libraries/ngs2/ngs2.cpp
src/core/libraries/ngs2/ngs2.h
)
@@ -599,6 +601,9 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/trophy_ui.h
src/core/libraries/np/np_web_api.cpp
src/core/libraries/np/np_web_api.h
+ src/core/libraries/np/np_web_api_error.h
+ src/core/libraries/np/np_web_api_internal.cpp
+ src/core/libraries/np/np_web_api_internal.h
src/core/libraries/np/np_web_api2.cpp
src/core/libraries/np/np_web_api2.h
src/core/libraries/np/np_party.cpp
diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml
index d2a6747d9..210ca1c5e 100644
--- a/dist/net.shadps4.shadPS4.metainfo.xml
+++ b/dist/net.shadps4.shadPS4.metainfo.xml
@@ -38,6 +38,9 @@
Game
+
+ https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0
+
https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index db03e7679..80a6ff7e2 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -271,7 +271,8 @@ add_subdirectory(json)
add_subdirectory(miniz)
# cli11
-set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
-set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
-
-add_subdirectory(CLI11)
\ No newline at end of file
+if (NOT TARGET CLI11::CLI11)
+ set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+ set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
+ add_subdirectory(CLI11)
+endif()
diff --git a/src/common/config.cpp b/src/common/config.cpp
index eac463d0a..aece6916f 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -1299,6 +1299,9 @@ hotkey_toggle_mouse_to_joystick = f7
hotkey_toggle_mouse_to_gyro = f6
hotkey_toggle_mouse_to_touchpad = delete
hotkey_quit = lctrl, lshift, end
+
+hotkey_volume_up = kpplus
+hotkey_volume_down = kpminus
)";
}
diff --git a/src/common/elf_info.h b/src/common/elf_info.h
index 0b2589e95..0f2311cb0 100644
--- a/src/common/elf_info.h
+++ b/src/common/elf_info.h
@@ -75,6 +75,7 @@ class ElfInfo {
std::filesystem::path game_folder{};
public:
+ static constexpr u32 FW_10 = 0x1000000;
static constexpr u32 FW_15 = 0x1500000;
static constexpr u32 FW_16 = 0x1600000;
static constexpr u32 FW_17 = 0x1700000;
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index d6daaa852..e56953fb6 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -175,7 +175,7 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
- Libraries::Kernel::g_curthread->name = std::string{name};
+ Libraries::Kernel::g_curthread->name = name;
}
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
}
@@ -190,7 +190,7 @@ void SetThreadName(void* thread, const char* name) {
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
- Libraries::Kernel::g_curthread->name = std::string{name};
+ Libraries::Kernel::g_curthread->name = name;
}
#ifdef __APPLE__
pthread_setname_np(name);
@@ -219,7 +219,7 @@ void SetThreadName(void* thread, const char* name) {
#if defined(_WIN32)
void SetCurrentThreadName(const char*) {
if (Libraries::Kernel::g_curthread) {
- Libraries::Kernel::g_curthread->name = std::string{name};
+ Libraries::Kernel::g_curthread->name = name;
}
// Do Nothing on MinGW
}
diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp
index f4ce22b8b..d1c9374cc 100644
--- a/src/core/libraries/ajm/ajm_mp3.cpp
+++ b/src/core/libraries/ajm/ajm_mp3.cpp
@@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "ajm_error.h"
+#include "ajm_mp3.h"
+#include "ajm_result.h"
+
#include "common/assert.h"
-#include "core/libraries/ajm/ajm_error.h"
-#include "core/libraries/ajm/ajm_mp3.h"
#include "core/libraries/error_codes.h"
extern "C" {
@@ -122,7 +124,6 @@ void AjmMp3Decoder::Reset() {
avcodec_flush_buffers(m_codec_context);
m_header.reset();
m_frame_samples = 0;
- m_frame_size = 0;
}
void AjmMp3Decoder::GetInfo(void* out_info) const {
@@ -141,7 +142,7 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
u32 AjmMp3Decoder::GetMinimumInputSize() const {
// 4 bytes is for mp3 header that contains frame_size
- return std::max(m_frame_size, 4);
+ return 4;
}
DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output,
@@ -149,12 +150,11 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuff
DecoderResult result{};
AVPacket* pkt = av_packet_alloc();
- if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
- m_header = std::byteswap(*reinterpret_cast(in_buf.data()));
- AjmDecMp3ParseFrame info{};
- ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
- m_frame_samples = info.samples_per_channel;
- m_frame_size = info.frame_size;
+ m_header = std::byteswap(*reinterpret_cast(in_buf.data()));
+ AjmDecMp3ParseFrame info{};
+ ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
+ m_frame_samples = info.samples_per_channel;
+ if (info.total_samples != 0 || info.encoder_delay != 0) {
gapless.init = {
.total_samples = info.total_samples,
.skip_samples = static_cast(info.encoder_delay),
@@ -163,6 +163,10 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuff
gapless.current = gapless.init;
}
+ if (in_buf.size() < info.frame_size) {
+ result.result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
+ }
+
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
@@ -424,11 +428,7 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
frame->encoder_delay = std::byteswap(*reinterpret_cast(p_fgh + 1));
frame->total_samples = std::byteswap(*reinterpret_cast(p_fgh + 3));
frame->ofl_type = AjmDecMp3OflType::Fgh;
- } else {
- LOG_ERROR(Lib_Ajm, "FGH header CRC is incorrect.");
}
- } else {
- LOG_ERROR(Lib_Ajm, "Could not find vendor header.");
}
}
diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h
index ecbc77051..1113e222a 100644
--- a/src/core/libraries/ajm/ajm_mp3.h
+++ b/src/core/libraries/ajm/ajm_mp3.h
@@ -99,7 +99,6 @@ private:
SwrContext* m_swr_context = nullptr;
std::optional m_header;
u32 m_frame_samples = 0;
- u32 m_frame_size = 0;
};
} // namespace Libraries::Ajm
diff --git a/src/core/libraries/audio/audioin.cpp b/src/core/libraries/audio/audioin.cpp
index 563b5ae14..55c2891b2 100644
--- a/src/core/libraries/audio/audioin.cpp
+++ b/src/core/libraries/audio/audioin.cpp
@@ -1,23 +1,264 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include
+#include "audioin_backend.h"
+#include "audioin_error.h"
#include "common/logging/log.h"
#include "core/libraries/audio/audioin.h"
-#include "core/libraries/audio/sdl_in.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
namespace Libraries::AudioIn {
-static std::unique_ptr audio = std::make_unique();
+std::array, ORBIS_AUDIO_IN_NUM_PORTS> port_table{};
+std::shared_mutex port_table_mutex;
+std::mutex port_allocation_mutex;
-int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
- LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
- return ORBIS_OK;
+static std::unique_ptr audio;
+
+/*
+ * Helper functions
+ **/
+static int GetPortId(s32 handle) {
+ int port_id = handle & 0xFF;
+
+ if (port_id >= ORBIS_AUDIO_IN_NUM_PORTS) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port");
+ return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
+ }
+
+ if ((handle & 0x7f000000) != 0x30000000) {
+ LOG_ERROR(Lib_AudioIn, "Invalid handle format");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+
+ return port_id;
+}
+
+static s32 GetPortType(s32 handle) {
+ return (handle >> 16) & 0xFF;
+}
+
+static int AllocatePort(OrbisAudioInType type) {
+ // TODO implement port type ranges if needed
+ for (int i = 0; i <= ORBIS_AUDIO_IN_NUM_PORTS; i++) {
+ std::shared_lock read_lock{port_table_mutex};
+ if (!port_table[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+/*
+ * sceAudioIn implementation
+ **/
+static bool initOnce = false;
+int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
+ u32 index, u32 len, u32 freq, u32 param) {
+ LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
+ type, index, len, freq, param);
+ if (!initOnce) {
+ // sceAudioInInit doesn't seem to be called by most apps before sceAudioInOpen so we init
+ // here
+ audio = std::make_unique();
+ initOnce = true;
+ }
+
+ if (len == 0 || len > 2048) {
+ LOG_ERROR(Lib_AudioIn, "Invalid size");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_SIZE;
+ }
+
+ // Validate parameters
+ OrbisAudioInType in_type = static_cast(type);
+ OrbisAudioInParamFormat format = static_cast(param);
+
+ if (format != OrbisAudioInParamFormat::S16Mono &&
+ format != OrbisAudioInParamFormat::S16Stereo) {
+ LOG_ERROR(Lib_AudioIn, "Invalid format");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
+ }
+
+ if (freq != 16000 && freq != 48000) {
+ LOG_ERROR(Lib_AudioIn, "Invalid sample rate");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_FREQ;
+ }
+
+ std::unique_lock lock{port_allocation_mutex};
+
+ // Allocate port
+ int port_id = AllocatePort(in_type);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioIn, "No free audio input ports available");
+ return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
+ }
+
+ // Create port object
+ std::shared_ptr port;
+ try {
+ port = std::make_shared();
+
+ port->type = in_type;
+ port->format = format;
+ port->samples_num = len;
+ port->freq = freq;
+
+ // Determine channel count and sample size based on format
+ switch (format) {
+ case OrbisAudioInParamFormat::S16Mono:
+ port->channels_num = 1;
+ port->sample_size = 2;
+ break;
+ case OrbisAudioInParamFormat::S16Stereo:
+ port->channels_num = 2;
+ port->sample_size = 2;
+ break;
+ default:
+ LOG_ERROR(Lib_AudioIn, "Unsupported audio format: {}", static_cast(format));
+ return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
+ }
+
+ // Open backend
+ port->impl = audio->Open(*port);
+ if (!port->impl) {
+ throw std::runtime_error("Failed to create audio backend");
+ }
+
+ } catch (const std::bad_alloc&) {
+ LOG_ERROR(Lib_AudioIn, "Failed to allocate memory for audio port");
+ return ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY;
+ } catch (const std::exception& e) {
+ LOG_ERROR(Lib_AudioIn, "Failed to open audio input port: {}", e.what());
+ return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
+ }
+
+ // Store the port pointer with write lock
+ {
+ std::unique_lock write_lock{port_table_mutex};
+ port_table[port_id] = port;
+ }
+
+ // Create handle
+ s32 handle = (type << 16) | port_id | 0x30000000;
+
+ LOG_INFO(Lib_AudioIn, "Opened audio input port {}: type={}, samples={}, freq={}, format={}",
+ handle, static_cast(in_type), len, freq, static_cast(format));
+ return handle;
+}
+
+int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
+ u32 index, u32 len, u32 freq, u32 param) {
+ LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
+ type, index, len, freq, param);
+ int result = sceAudioInOpen(userId, type, index, len, freq, param);
+ if (result < 0) {
+ LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
+ }
+ return result;
}
int PS4_SYSV_ABI sceAudioInClose(s32 handle) {
- audio->AudioInClose(handle);
+ LOG_INFO(Lib_AudioIn, "called, handle={:#x}", handle);
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port id");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+
+ std::unique_lock lock{port_allocation_mutex};
+ std::shared_ptr port;
+
+ // Get and clear the port pointer with write lock
+ {
+ std::unique_lock write_lock{port_table_mutex};
+ port = std::move(port_table[port_id]);
+ if (!port) {
+ LOG_ERROR(Lib_AudioIn, "Port wasn't open {}", port_id);
+ return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
+ }
+ port_table[port_id].reset();
+ }
+
+ // Free resources
+ std::scoped_lock port_lock{port->mutex};
+ port->impl.reset();
+
+ LOG_INFO(Lib_AudioIn, "Closed audio input port {}", handle);
+ return ORBIS_OK;
+}
+
+int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
+ LOG_TRACE(Lib_AudioIn, "called, handle={:#x}, dest={}", handle, fmt::ptr(dest));
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port id");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+
+ if (!dest) {
+ LOG_ERROR(Lib_AudioIn, "Invalid output buffer pointer");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_POINTER;
+ }
+
+ // Get port with read lock
+ std::shared_ptr port;
+ {
+ std::shared_lock read_lock{port_table_mutex};
+ if (port_id < 0 || port_id >= static_cast(port_table.size())) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+ port = port_table[port_id];
+ }
+
+ if (!port || !port->impl) {
+ LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
+ return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
+ }
+
+ std::scoped_lock lock{port->mutex};
+ return port->impl->Read(dest);
+}
+
+int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle) {
+ LOG_TRACE(Lib_AudioIn, "called, handle={:#x}", handle);
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port id");
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+ // Get port with read lock
+ std::shared_ptr port;
+ {
+ std::shared_lock read_lock{port_table_mutex};
+ if (port_id < 0 || port_id >= static_cast(port_table.size())) {
+ LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
+ return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
+ }
+ port = port_table[port_id];
+ }
+
+ if (!port || !port->impl) {
+ LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
+ return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
+ }
+
+ u32 silent_state = 0;
+ std::scoped_lock lock{port->mutex};
+ if (!port->impl->IsAvailable()) { // if no mic exist or is not available
+ silent_state |= ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE;
+ }
+ return silent_state;
+}
+
+/*
+ * Stubbed functions
+ **/
+int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
+ LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
return ORBIS_OK;
}
@@ -91,20 +332,6 @@ int PS4_SYSV_ABI sceAudioInGetRerouteCount() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioInGetSilentState() {
- LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
- u32 index, u32 len, u32 freq, u32 param) {
- int result = audio->AudioInOpen(type, len, freq, param);
- if (result < 0) {
- LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
- }
- return result;
-}
-
int PS4_SYSV_ABI sceAudioInHqOpenEx() {
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
return ORBIS_OK;
@@ -115,10 +342,6 @@ int PS4_SYSV_ABI sceAudioInInit() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
- return audio->AudioInInput(handle, dest);
-}
-
int PS4_SYSV_ABI sceAudioInInputs() {
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
return ORBIS_OK;
@@ -129,15 +352,6 @@ int PS4_SYSV_ABI sceAudioInIsSharedDevice() {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
- u32 index, u32 len, u32 freq, u32 param) {
- int result = audio->AudioInOpen(type, len, freq, param);
- if (result < 0) {
- LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
- }
- return result;
-}
-
int PS4_SYSV_ABI sceAudioInOpenEx() {
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
return ORBIS_OK;
diff --git a/src/core/libraries/audio/audioin.h b/src/core/libraries/audio/audioin.h
index f528c730e..0eda2013e 100644
--- a/src/core/libraries/audio/audioin.h
+++ b/src/core/libraries/audio/audioin.h
@@ -3,6 +3,7 @@
#pragma once
+#include
#include
#include "common/types.h"
@@ -12,10 +13,31 @@ class SymbolsResolver;
namespace Libraries::AudioIn {
+class PortInBackend;
+
+constexpr s32 ORBIS_AUDIO_IN_NUM_PORTS = 7;
+
enum class OrbisAudioInParamFormat : u32 { S16Mono = 0, S16Stereo = 2 };
enum class OrbisAudioInType : u32 { VoiceChat = 0, General = 1, VoiceRecognition = 5 };
+constexpr int ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x00000001;
+constexpr int ORBIS_AUDIO_IN_SILENT_STATE_PRIORITY_LOW = 0x00000002;
+constexpr int ORBIS_AUDIO_IN_SILENT_STATE_USER_SETTING = 0x0000000;
+constexpr int ORBIS_AUDIO_IN_SILENT_STATE_UNABLE_FORMAT = 0x00000008;
+
+struct PortIn {
+ std::mutex mutex;
+ std::unique_ptr impl{};
+ OrbisAudioInType type;
+ OrbisAudioInParamFormat format;
+
+ u32 samples_num = 0;
+ u32 freq = 0;
+ u32 channels_num = 0;
+ u32 sample_size = 0;
+};
+
int PS4_SYSV_ABI sceAudioInChangeAppModuleState();
int PS4_SYSV_ABI sceAudioInClose(s32 handle);
int PS4_SYSV_ABI sceAudioInCountPorts();
@@ -32,7 +54,7 @@ int PS4_SYSV_ABI sceAudioInExtSetAecMode();
int PS4_SYSV_ABI sceAudioInGetGain();
int PS4_SYSV_ABI sceAudioInGetHandleStatusInfo();
int PS4_SYSV_ABI sceAudioInGetRerouteCount();
-int PS4_SYSV_ABI sceAudioInGetSilentState();
+int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle);
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
u32 index, u32 len, u32 freq, u32 param);
int PS4_SYSV_ABI sceAudioInHqOpenEx();
diff --git a/src/core/libraries/audio/audioin_backend.h b/src/core/libraries/audio/audioin_backend.h
new file mode 100644
index 000000000..dc06bb27c
--- /dev/null
+++ b/src/core/libraries/audio/audioin_backend.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+#include
+
+namespace Libraries::AudioIn {
+
+struct PortIn;
+
+class PortInBackend {
+public:
+ virtual ~PortInBackend() = default;
+ virtual int Read(void* out_buffer) = 0;
+ virtual void Clear() = 0;
+ virtual bool IsAvailable() = 0;
+};
+
+class AudioInBackend {
+public:
+ AudioInBackend() = default;
+ virtual ~AudioInBackend() = default;
+ virtual std::unique_ptr Open(PortIn& port) = 0;
+};
+
+class SDLAudioIn final : public AudioInBackend {
+public:
+ std::unique_ptr Open(PortIn& port) override;
+};
+
+} // namespace Libraries::AudioIn
\ No newline at end of file
diff --git a/src/core/libraries/audio/audioin_error.h b/src/core/libraries/audio/audioin_error.h
new file mode 100644
index 000000000..c392e8194
--- /dev/null
+++ b/src/core/libraries/audio/audioin_error.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/libraries/error_codes.h"
+
+// AudioIn library
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE = 0x80260101;
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_SIZE = 0x80260102;
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_FREQ = 0x80260103;
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_TYPE = 0x80260104;
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_POINTER = 0x80260105;
+constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_PARAM = 0x80260106;
+constexpr int ORBIS_AUDIO_IN_ERROR_PORT_FULL = 0x80260107;
+constexpr int ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY = 0x80260108;
+constexpr int ORBIS_AUDIO_IN_ERROR_NOT_OPENED = 0x80260109;
+constexpr int ORBIS_AUDIO_IN_ERROR_BUSY = 0x8026010A;
+constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_MEMORY = 0x8026010B;
+constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_IPC = 0x8026010C;
diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp
index bb1ca65fe..6c58df94f 100644
--- a/src/core/libraries/audio/audioout.cpp
+++ b/src/core/libraries/audio/audioout.cpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include
#include
#include
@@ -19,315 +20,238 @@
namespace Libraries::AudioOut {
-std::mutex port_open_mutex{};
-std::array ports_out{};
+// Port table with shared_ptr - use std::shared_mutex for RW locking
+std::array, ORBIS_AUDIO_OUT_NUM_PORTS> port_table{};
+std::shared_mutex port_table_mutex;
+std::mutex port_allocation_mutex;
static std::unique_ptr audio;
+static std::atomic lazy_init{0};
+
+// Port allocation ranges
+constexpr struct PortRange {
+ s32 start;
+ s32 end;
+ s32 count() const {
+ return end - start + 1;
+ }
+} port_ranges[] = {
+ {0, 7}, // MAIN
+ {8, 8}, // BGM
+ {9, 12}, // VOICE
+ {13, 16}, // PERSONAL
+ {17, 20}, // PADSPK
+ {21, 21}, // Type 5-8
+ {22, 22}, // Audio3d (126)
+ {23, 23}, // AUX (127)
+ {24, 24}, // Type 125
+};
static AudioFormatInfo GetFormatInfo(const OrbisAudioOutParamFormat format) {
static constexpr std::array format_infos = {{
// S16Mono
- {false, 2, 1, {0}},
+ {false, 2, 1, {0}, false},
// S16Stereo
- {false, 2, 2, {0, 1}},
+ {false, 2, 2, {0, 1}, false},
// S16_8CH
- {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
+ {false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}, false},
// FloatMono
- {true, 4, 1, {0}},
+ {true, 4, 1, {0}, false},
// FloatStereo
- {true, 4, 2, {0, 1}},
+ {true, 4, 2, {0, 1}, false},
// Float_8CH
- {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
+ {true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}, false},
// S16_8CH_Std
- {false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
+ {false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}, true},
// Float_8CH_Std
- {true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
+ {true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}, true},
}};
const auto index = static_cast(format);
ASSERT_MSG(index < format_infos.size(), "Unknown audio format {}", index);
return format_infos[index];
}
-int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
+/*
+ * Helper functions
+ **/
+static int GetPortRange(OrbisAudioOutPort type) {
+ s32 _type = static_cast(type);
-int PS4_SYSV_ABI sceAudioDeviceControlGet() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioDeviceControlSet() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutA3dControl() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutA3dExit() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutA3dInit() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
- LOG_INFO(Lib_AudioOut, "handle = {}", handle);
- if (audio == nullptr) {
- return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ switch (_type) {
+ case 0:
+ return 0; // MAIN
+ case 1:
+ return 1; // BGM
+ case 2:
+ return 2; // VOICE
+ case 3:
+ return 3; // PERSONAL
+ case 4:
+ return 4; // PADSPK
+ case 5:
+ return 5; // Type 5
+ case 6:
+ return 5; // Type 6
+ case 7:
+ return 5; // Type 7
+ case 8:
+ return 5; // Type 8
+ case 126:
+ return 6; // Audio3d
+ case 125:
+ return 8; // Type 125
+ case 127:
+ return 7; // AUX
+ default:
+ return -1;
}
- if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
+}
+
+static int GetPortId(s32 handle) {
+ int port_id = handle & 0xFF;
+
+ if (port_id >= ORBIS_AUDIO_OUT_NUM_PORTS) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port");
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
- std::unique_lock open_lock{port_open_mutex};
- auto& port = ports_out.at(handle - 1);
- {
- std::unique_lock lock{port.mutex};
- if (!port.IsOpen()) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
- }
- std::free(port.output_buffer);
- port.output_buffer = nullptr;
- port.output_ready = false;
- port.impl = nullptr;
- }
- // Stop outside of port lock scope to prevent deadlocks.
- port.output_thread.Stop();
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExGetSystemInfo() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExPtClose() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExPtOpen() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetInfo() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time) {
- LOG_DEBUG(Lib_AudioOut, "called, handle: {}, output time: {}", handle, fmt::ptr(output_time));
- if (!output_time) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
- }
- if (handle >= ports_out.size()) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
- }
- auto& port = ports_out.at(handle - 1);
- if (!port.IsOpen()) {
- return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
- }
- *output_time = port.last_output_time;
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
- if (audio == nullptr) {
- return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
- }
- if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
+ if ((handle & 0x3F000000) != 0x20000000) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port");
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
- auto& port = ports_out.at(handle - 1);
- {
- std::unique_lock lock{port.mutex};
- if (!port.IsOpen()) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
- }
- switch (port.type) {
- case OrbisAudioOutPort::Main:
- case OrbisAudioOutPort::Bgm:
- case OrbisAudioOutPort::Voice:
- case OrbisAudioOutPort::Audio3d:
- state->output = 1;
- state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
- break;
- case OrbisAudioOutPort::Personal:
- case OrbisAudioOutPort::PadSpk:
- state->output = 4;
- state->channel = 1;
- break;
- case OrbisAudioOutPort::Aux:
- state->output = 0;
- state->channel = 0;
- break;
- default:
- UNREACHABLE();
- }
- state->rerouteCounter = 0;
- state->volume = 127;
+ return port_id;
+}
+
+static s32 GetPortType(s32 handle) {
+ return (handle >> 16) & 0xFF;
+}
+
+static int AllocatePort(OrbisAudioOutPort type) {
+ int range_idx = GetPortRange(type);
+ if (range_idx < 0) {
+ return -1;
}
- return ORBIS_OK;
-}
-int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetSparkVss() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutGetSystemState() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutInit() {
- LOG_TRACE(Lib_AudioOut, "called");
- if (audio != nullptr) {
- return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
+ const auto& range = port_ranges[range_idx];
+ for (int i = range.start; i <= range.end; i++) {
+ std::shared_lock read_lock{port_table_mutex};
+ if (!port_table[i]) {
+ return i;
+ }
}
- audio = std::make_unique();
- return ORBIS_OK;
+ return -1;
}
-int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
+void AdjustVol() {
+ if (lazy_init.load(std::memory_order_relaxed) == 0 && audio == nullptr) {
+ return;
+ }
+
+ std::shared_lock read_lock{port_table_mutex};
+ for (int i = 0; i < ORBIS_AUDIO_OUT_NUM_PORTS; i++) {
+ if (auto port = port_table[i]) {
+ std::unique_lock lock{port->mutex, std::try_to_lock};
+ if (lock.owns_lock()) {
+ port->impl->SetVolume(port->volume);
+ }
+ }
+ }
}
-int PS4_SYSV_ABI sceAudioOutMasteringGetState() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutMasteringInit() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutMasteringSetParam() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutMasteringTerm() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutMbusInit() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-static void AudioOutputThread(PortOut* port, const std::stop_token& stop) {
+static void AudioOutputThread(std::shared_ptr port, const std::stop_token& stop) {
{
- const auto thread_name = fmt::format("shadPS4:AudioOutputThread:{}", fmt::ptr(port));
+ const auto thread_name = fmt::format("shadPS4:AudioOutputThread:{}", fmt::ptr(port.get()));
Common::SetCurrentThreadName(thread_name.c_str());
}
Common::AccurateTimer timer(
std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->sample_rate));
+
while (true) {
timer.Start();
+
{
std::unique_lock lock{port->mutex};
+ if (!port->impl || stop.stop_requested()) {
+ break;
+ }
+
if (port->output_ready) {
port->impl->Output(port->output_buffer);
port->output_ready = false;
+ port->last_output_time =
+ Kernel::sceKernelGetProcessTime(); // moved from sceAudioOutOutput TOOD recheck
}
}
+
port->output_cv.notify_one();
+
if (stop.stop_requested()) {
break;
}
+
timer.End();
}
}
+/*
+ * sceAudioOut implementation
+ **/
+s32 PS4_SYSV_ABI sceAudioOutInit() {
+ LOG_TRACE(Lib_AudioOut, "called");
+
+ int expected = 0;
+ if (!lazy_init.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) {
+ return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
+ }
+
+ audio = std::make_unique();
+
+ LOG_INFO(Lib_AudioOut, "Audio system initialized");
+ return ORBIS_OK;
+}
+
s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
OrbisAudioOutPort port_type, s32 index, u32 length,
u32 sample_rate,
OrbisAudioOutParamExtendedInformation param_type) {
LOG_INFO(Lib_AudioOut,
- "id = {} port_type = {} index = {} length = {} sample_rate = {} "
- "param_type = {} attr = {}",
- user_id, magic_enum::enum_name(port_type), index, length, sample_rate,
- magic_enum::enum_name(param_type.data_format.Value()),
- magic_enum::enum_name(param_type.attributes.Value()));
- if (audio == nullptr) {
+ "called, user_id={}, port_type={}({}), index={}, length={}, "
+ "sample_rate={}, data_format={}({}), attributes={}({})",
+ user_id, magic_enum::enum_name(port_type), static_cast(port_type), index, length,
+ sample_rate, magic_enum::enum_name(param_type.data_format.Value()),
+ static_cast(param_type.data_format.Value()),
+ magic_enum::enum_name(param_type.attributes.Value()),
+ static_cast(param_type.attributes.Value()));
+
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
LOG_ERROR(Lib_AudioOut, "Audio out not initialized");
return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
}
+
+ if (length == 0 || length > 2048 || (length & 0xFF) != 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid size");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE;
+ }
+
+ s32 _type = static_cast(port_type);
+ u32 param_raw = param_type.Unpack();
+
+ // Extract attributes
+ bool is_restricted = (param_raw & ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED) != 0;
+ bool is_mix_to_main = (param_raw & ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN) != 0;
+
+ if (_type != 3 && is_mix_to_main) {
+ LOG_ERROR(Lib_AudioOut, "Invalid format");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
+ }
+
+ if (_type != 0 && (param_raw & 0x70000000) != 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid format");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
+ }
+
if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::PadSpk) &&
(port_type != OrbisAudioOutPort::Audio3d && port_type != OrbisAudioOutPort::Aux)) {
LOG_ERROR(Lib_AudioOut, "Invalid port type");
@@ -337,14 +261,11 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_ERROR(Lib_AudioOut, "Invalid sample rate");
return ORBIS_AUDIO_OUT_ERROR_INVALID_SAMPLE_FREQ;
}
- if (length != 256 && length != 512 && length != 768 && length != 1024 && length != 1280 &&
- length != 1536 && length != 1792 && length != 2048) {
- LOG_ERROR(Lib_AudioOut, "Invalid length");
- return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE;
- }
+
if (index != 0) {
LOG_ERROR(Lib_AudioOut, "index is not valid !=0 {}", index);
}
+
const auto format = param_type.data_format.Value();
if (format < OrbisAudioOutParamFormat::S16Mono ||
format > OrbisAudioOutParamFormat::Float_8CH_Std) {
@@ -358,270 +279,808 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
}
- std::unique_lock open_lock{port_open_mutex};
- const auto port =
- std::ranges::find_if(ports_out, [&](const PortOut& p) { return !p.IsOpen(); });
- if (port == ports_out.end()) {
- LOG_ERROR(Lib_AudioOut, "Audio ports are full");
+ std::unique_lock lock{port_allocation_mutex};
+
+ // Allocate port
+ int port_id = AllocatePort(port_type);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "Error allocated port");
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
}
- {
- std::unique_lock port_lock(port->mutex);
+ // Create port object
+ std::shared_ptr port;
+ try {
+ port = std::make_shared();
- port->type = port_type;
- port->format_info = GetFormatInfo(format);
+ port->userId = user_id;
+ port->type = static_cast(_type);
+ port->format_info = GetFormatInfo(param_type.data_format.Value());
port->sample_rate = sample_rate;
port->buffer_frames = length;
- port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
+ port->volume.fill(ORBIS_AUDIO_OUT_VOLUME_0DB);
+ port->mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT;
+ // Set attributes
+ port->is_restricted = is_restricted;
+ port->is_mix_to_main = is_mix_to_main;
+
+ // Log attributes if present
+ if (is_restricted) {
+ LOG_INFO(Lib_AudioOut, "Audio port opened with RESTRICTED attribute");
+ }
+ if (is_mix_to_main) {
+ LOG_INFO(Lib_AudioOut, "Audio port opened with MIX_TO_MAIN attribute");
+ }
+
+ // Create backend
port->impl = audio->Open(*port);
+ if (!port->impl) {
+ throw std::runtime_error("Failed to create audio backend");
+ }
+ // Allocate buffer
port->output_buffer = std::malloc(port->BufferSize());
- port->output_ready = false;
+ if (!port->output_buffer) {
+ throw std::bad_alloc();
+ }
+
+ // Start output thread - pass shared_ptr by value to keep port alive
port->output_thread.Run(
- [port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); });
+ [port](const std::stop_token& stop) { AudioOutputThread(port, stop); });
+
+ // Set initial volume
+ port->impl->SetVolume(port->volume);
+
+ } catch (const std::bad_alloc&) {
+ return ORBIS_AUDIO_OUT_ERROR_OUT_OF_MEMORY;
+ } catch (const std::exception& e) {
+ LOG_ERROR(Lib_AudioOut, "Failed to open audio port: {}", e.what());
+ return ORBIS_AUDIO_OUT_ERROR_TRANS_EVENT;
}
- return std::distance(ports_out.begin(), port) + 1;
+
+ {
+ std::unique_lock write_lock{port_table_mutex};
+ port_table[port_id] = port;
+ }
+
+ // Create handle
+ s32 handle = (_type << 16) | port_id | 0x20000000;
+ return handle;
}
-int PS4_SYSV_ABI sceAudioOutOpenEx() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
+ LOG_INFO(Lib_AudioOut, "handle = {:#x}", handle);
+
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "Audio out not initialized");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port id");
+ return port_id;
+ }
+
+ s32 port_type = GetPortType(handle);
+ if (port_type >= 5 && port_type <= 13) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ // Check valid types
+ if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 ||
+ port_type == 127)) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ std::unique_lock lock{port_allocation_mutex};
+
+ std::shared_ptr port;
+ {
+ std::unique_lock write_lock{port_table_mutex};
+ port = std::move(port_table[port_id]);
+ port_table[port_id].reset();
+ }
+
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port wasn't open {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ // Stop the output thread
+ port->output_thread.Stop();
+
+ std::free(port->output_buffer);
+
+ LOG_DEBUG(Lib_AudioOut, "Closed audio port {}", port_id);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time) {
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port id");
+ return port_id;
+ }
+
+ if (!output_time) {
+ LOG_ERROR(Lib_AudioOut, "Invalid pointer");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
+ }
+
+ std::shared_ptr port;
+ {
+ std::shared_lock read_lock{port_table_mutex};
+ port = port_table[port_id];
+ }
+
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ std::unique_lock lock{port->mutex};
+ *output_time = port->last_output_time;
+
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port id");
+ return port_id;
+ }
+
+ if (!state) {
+ LOG_ERROR(Lib_AudioOut, "Invalid pointer");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
+ }
+
+ std::shared_ptr port;
+ {
+ std::shared_lock read_lock{port_table_mutex};
+ port = port_table[port_id];
+ }
+
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port is not open {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ std::unique_lock lock{port->mutex};
+
+ switch (port->type) {
+ case OrbisAudioOutPort::Main:
+ case OrbisAudioOutPort::Bgm:
+ case OrbisAudioOutPort::Audio3d:
+ state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY;
+ state->channel = port->format_info.num_channels > 2 ? 2 : port->format_info.num_channels;
+ break;
+ case OrbisAudioOutPort::Voice:
+ case OrbisAudioOutPort::Personal:
+ state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE;
+ state->channel = 1;
+ break;
+ case OrbisAudioOutPort::PadSpk:
+ state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY;
+ state->channel = 1;
+ state->volume = 127; // max
+ break;
+ case OrbisAudioOutPort::Aux:
+ state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL;
+ state->channel = 0;
+ break;
+ default:
+ state->output = ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN;
+ state->channel = 0;
+ break;
+ }
+
+ if (port->type != OrbisAudioOutPort::PadSpk) {
+ state->volume = -1; // invalid
+ }
+
+ state->rerouteCounter = 0;
+ state->flag = 0;
+ LOG_INFO(Lib_AudioOut,
+ "called, handle={:#x}, state={}, output={}, channel={}, volume={}, "
+ "rerouteCounter={}, flag={}",
+ handle, fmt::ptr(state), state->output, state->channel, state->volume,
+ state->rerouteCounter, state->flag);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
- if (audio == nullptr) {
+ LOG_TRACE(Lib_AudioOut, "(STUBBED) called, handle={:#x}, ptr={}", handle, fmt::ptr(ptr));
+
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
}
- if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "invalid port id");
+ return port_id;
}
- auto samples_sent = 0;
- auto& port = ports_out.at(handle - 1);
+ s32 port_type = GetPortType(handle);
+ if (port_type >= 5 && port_type <= 13) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ // Check valid types
+ if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 ||
+ port_type == 127)) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ std::shared_ptr port;
{
- std::unique_lock lock{port.mutex};
- if (!port.IsOpen()) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
- }
- port.output_cv.wait(lock, [&] { return !port.output_ready; });
- if (ptr != nullptr && port.IsOpen()) {
- std::memcpy(port.output_buffer, ptr, port.BufferSize());
- port.output_ready = true;
- port.last_output_time = Kernel::sceKernelGetProcessTime();
- samples_sent = port.buffer_frames * port.format_info.num_channels;
+ std::shared_lock read_lock{port_table_mutex};
+ port = port_table[port_id];
+ }
+
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port is not opened {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ s32 samples_sent = 0;
+ {
+ std::unique_lock lock{port->mutex};
+ port->output_cv.wait(lock, [&] { return !port->output_ready; });
+
+ if (ptr != nullptr) {
+ std::memcpy(port->output_buffer, ptr, port->BufferSize());
+ port->output_ready = true;
+ samples_sent = port->buffer_frames * port->format_info.num_channels;
}
}
+
return samples_sent;
}
-int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
- int ret = 0;
- for (u32 i = 0; i < num; i++) {
- const auto [handle, ptr] = param[i];
- if (ret = sceAudioOutOutput(handle, ptr); ret < 0) {
- return ret;
+s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
+ if (param) {
+ LOG_TRACE(Lib_AudioOut, "(STUBBED) called, param={}, num={}", fmt::ptr(param), num);
+ for (u32 i = 0; i < num; i++) {
+ LOG_TRACE(Lib_AudioOut, " [{}] handle={:#x}, ptr={}", i, param[i].handle,
+ fmt::ptr(param[i].ptr));
+ }
+ } else {
+ LOG_TRACE(Lib_AudioOut, "(STUBBED) called, param=nullptr, num={}", num);
+ }
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
+
+ if (num == 0 || num > 25) {
+ LOG_ERROR(Lib_AudioOut, "ports is full");
+ return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
+ }
+
+ if (!param) {
+ LOG_ERROR(Lib_AudioOut, "invalid pointer");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
+ }
+
+ std::vector> ports;
+ std::vector> locks;
+ ports.reserve(num);
+ locks.reserve(num);
+
+ u32 buffer_frames = 0;
+
+ {
+ std::shared_lock read_lock{port_table_mutex};
+
+ for (u32 i = 0; i < num; i++) {
+ int port_id = GetPortId(param[i].handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "invalid port id");
+ return port_id;
+ }
+
+ s32 port_type = GetPortType(param[i].handle);
+ if (port_type >= 5 && port_type <= 13) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ // Check valid types
+ if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 ||
+ port_type == 127)) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ // Check for duplicate handles
+ for (u32 j = 0; j < i; j++) {
+ if (param[i].handle == param[j].handle) {
+ LOG_ERROR(Lib_AudioOut, "Duplicate audio handles: {:#x}", param[i].handle);
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
+ }
+ }
+
+ // Get port
+ auto port = port_table[port_id];
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ ports.push_back(port);
+ locks.emplace_back(port->mutex);
+
+ // Check consistent buffer size
+ if (i == 0) {
+ buffer_frames = port->buffer_frames;
+ } else if (port->buffer_frames != buffer_frames) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port size");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_SIZE;
+ }
}
}
- return ret;
-}
-int PS4_SYSV_ABI sceAudioOutPtClose() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
+ // Wait for all ports to be ready
+ for (u32 i = 0; i < num; i++) {
+ ports[i]->output_cv.wait(locks[i], [&] { return !ports[i]->output_ready; });
+ }
-int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
+ // Copy data to all ports
+ for (u32 i = 0; i < num; i++) {
+ if (param[i].ptr != nullptr) {
+ std::memcpy(ports[i]->output_buffer, param[i].ptr, ports[i]->BufferSize());
+ ports[i]->output_ready = true;
+ }
+ }
-int PS4_SYSV_ABI sceAudioOutPtOpen() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetConnections() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetDevConnection() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetJediJackVolume() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetMainOutput() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetMorpheusParam() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetPortConnections() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetPortStatuses() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetRecMode() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetSparkParam() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-int PS4_SYSV_ABI sceAudioOutSetUsbVolume() {
- LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
- return ORBIS_OK;
+ return buffer_frames;
}
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
- if (audio == nullptr) {
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
}
- if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "invalid port_id");
+ return port_id;
}
- auto& port = ports_out.at(handle - 1);
+ s32 port_type = GetPortType(handle);
+ if (port_type >= 5 && port_type <= 13) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ // Check valid types
+ if (!((port_type >= 0 && port_type <= 4) || port_type == 14 || port_type == 126 ||
+ port_type == 127)) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ if (!vol) {
+ LOG_ERROR(Lib_AudioOut, "Invalid pointer");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
+ }
+
+ if (*vol > ORBIS_AUDIO_OUT_VOLUME_0DB) {
+ LOG_ERROR(Lib_AudioOut, "Invalid volume");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_VOLUME;
+ }
+
+ // Get port with shared lock (read-only access to table)
+ std::shared_ptr port;
{
- std::unique_lock lock{port.mutex};
- if (!port.IsOpen()) {
- return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
- }
- for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
- if (flag & 0x1u) {
- port.volume[i] = vol[i];
- }
- }
- port.impl->SetVolume(port.volume);
- }
- AdjustVol();
- return ORBIS_OK;
-}
-
-void AdjustVol() {
- if (audio == nullptr) {
- return;
+ std::shared_lock read_lock{port_table_mutex};
+ port = port_table[port_id];
}
- for (int i = 0; i < ports_out.size(); i++) {
- std::unique_lock lock{ports_out[i].mutex};
- if (!ports_out[i].IsOpen()) {
- continue;
- }
- ports_out[i].impl->SetVolume(ports_out[i].volume);
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port not opened {}", port_id);
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
}
+
+ std::unique_lock lock{port->mutex};
+
+ // Set volumes based on flags
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_L_CH)
+ port->volume[0] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_R_CH)
+ port->volume[1] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_C_CH)
+ port->volume[2] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_LFE_CH)
+ port->volume[3] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_LS_CH)
+ port->volume[4] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_RS_CH)
+ port->volume[5] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_LE_CH)
+ port->volume[6] = *vol;
+ if (flag & ORBIS_AUDIO_VOLUME_FLAG_RE_CH)
+ port->volume[7] = *vol;
+
+ port->impl->SetVolume(port->volume);
+
+ return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {
+s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel) {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ if (lazy_init.load(std::memory_order_relaxed) == 0 || audio == nullptr) {
+ LOG_ERROR(Lib_AudioOut, "audio is not init");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_INIT;
+ }
+
+ int port_id = GetPortId(handle);
+ if (port_id < 0) {
+ LOG_ERROR(Lib_AudioOut, "Invalid port_id");
+ return port_id;
+ }
+
+ if (GetPortType(handle) != 4) { // PadSpk
+ LOG_ERROR(Lib_AudioOut, "Invalid port type");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE;
+ }
+
+ if (mixLevel > ORBIS_AUDIO_OUT_VOLUME_0DB) {
+ LOG_ERROR(Lib_AudioOut, "Invalid mix level");
+ return ORBIS_AUDIO_OUT_ERROR_INVALID_MIXLEVEL;
+ }
+
+ std::shared_ptr port;
+ {
+ std::shared_lock read_lock{port_table_mutex};
+ port = port_table[port_id];
+ }
+
+ if (!port) {
+ LOG_ERROR(Lib_AudioOut, "Port not opened");
+ return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
+ }
+
+ std::unique_lock lock{port->mutex};
+ port->mixLevelPadSpk = mixLevel;
+ // TODO: Apply mix level to backend
+
+ return ORBIS_OK;
+}
+
+/*
+ * Stubbed functions
+ **/
+s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast() {
+s32 PS4_SYSV_ABI sceAudioDeviceControlGet() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutStartSharePlay() {
+s32 PS4_SYSV_ABI sceAudioDeviceControlSet() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast() {
+s32 PS4_SYSV_ABI sceAudioOutA3dControl() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutStopSharePlay() {
+s32 PS4_SYSV_ABI sceAudioOutA3dExit() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSuspendResume() {
+s32 PS4_SYSV_ABI sceAudioOutA3dInit() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode() {
+s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo() {
+s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo() {
+s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode() {
+s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSystemControlGet() {
+s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSystemControlSet() {
+s32 PS4_SYSV_ABI sceAudioOutExPtClose() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef() {
+s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAudioOutSetSystemDebugState() {
+s32 PS4_SYSV_ABI sceAudioOutExPtOpen() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetInfo() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutOpenEx() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetSparkVss() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutGetSystemState() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutMasteringGetState() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags) {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ if (flags != 0) {
+ return ORBIS_AUDIO_OUT_ERROR_MASTERING_INVALID_API_PARAM;
+ }
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutMasteringTerm() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutMbusInit() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutPtClose() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutPtOpen() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetConnections() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetDevConnection() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetMainOutput() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetPortConnections() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetRecMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetSparkParam() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutStartSharePlay() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutStopSharePlay() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSuspendResume() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSystemControlGet() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSystemControlSet() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef() {
+ LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK;
}
diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h
index c993582d3..93db8150f 100644
--- a/src/core/libraries/audio/audioout.h
+++ b/src/core/libraries/audio/audioout.h
@@ -17,8 +17,32 @@ class PortBackend;
// Main up to 8 ports, BGM 1 port, voice up to 4 ports,
// personal up to 4 ports, padspk up to 5 ports, aux 1 port
-constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22;
-constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
+constexpr s32 ORBIS_AUDIO_OUT_NUM_PORTS = 25;
+constexpr s32 ORBIS_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
+
+constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT = 11626; // default -9db
+constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_0DB = 32768; // max volume
+
+constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED = 0x00010000;
+constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN = 0x00020000;
+
+// Volume flags
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_L_CH = (1u << 0);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_R_CH = (1u << 1);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_C_CH = (1u << 2);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LFE_CH = (1u << 3);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LS_CH = (1u << 4);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RS_CH = (1u << 5);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LE_CH = (1u << 6);
+constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RE_CH = (1u << 7);
+
+// Port state constants
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN = 0x00;
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY = 0x01;
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_SECONDARY = 0x02;
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY = 0x04;
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE = 0x40;
+constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL = 0x80;
enum class OrbisAudioOutPort {
Main = 0,
@@ -53,6 +77,9 @@ union OrbisAudioOutParamExtendedInformation {
BitField<16, 4, OrbisAudioOutParamAttr> attributes;
BitField<20, 10, u32> reserve1;
BitField<31, 1, u32> unused;
+ u32 Unpack() const {
+ return *reinterpret_cast(this);
+ }
};
struct OrbisAudioOutOutputParam {
@@ -77,6 +104,7 @@ struct AudioFormatInfo {
/// Layout array remapping channel indices, specified in this order:
/// FL, FR, FC, LFE, BL, BR, SL, SR
std::array channel_layout;
+ bool is_std;
[[nodiscard]] u16 FrameSize() const {
return sample_size * num_channels;
@@ -87,100 +115,100 @@ struct PortOut {
std::mutex mutex;
std::unique_ptr impl{};
- void* output_buffer;
+ void* output_buffer = nullptr;
std::condition_variable_any output_cv;
- bool output_ready;
+ bool output_ready = false;
Kernel::Thread output_thread{};
OrbisAudioOutPort type;
AudioFormatInfo format_info;
- u32 sample_rate;
- u32 buffer_frames;
- u64 last_output_time;
+ u32 sample_rate = 48000;
+ u32 buffer_frames = 1024;
+ u64 last_output_time = 0;
std::array volume;
-
- [[nodiscard]] bool IsOpen() const {
- return impl != nullptr;
- }
+ s32 userId = 0;
+ s32 mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT;
+ bool is_restricted = false;
+ bool is_mix_to_main = false;
[[nodiscard]] u32 BufferSize() const {
return buffer_frames * format_info.FrameSize();
}
};
-int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
-int PS4_SYSV_ABI sceAudioDeviceControlGet();
-int PS4_SYSV_ABI sceAudioDeviceControlSet();
-int PS4_SYSV_ABI sceAudioOutA3dControl();
-int PS4_SYSV_ABI sceAudioOutA3dExit();
-int PS4_SYSV_ABI sceAudioOutA3dInit();
-int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
-int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
-int PS4_SYSV_ABI sceAudioOutClose(s32 handle);
-int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
-int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
-int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
-int PS4_SYSV_ABI sceAudioOutExPtClose();
-int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
-int PS4_SYSV_ABI sceAudioOutExPtOpen();
-int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
-int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
-int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
-int PS4_SYSV_ABI sceAudioOutGetInfo();
-int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
-int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
-int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
-int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
-int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
-int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
-int PS4_SYSV_ABI sceAudioOutGetSparkVss();
-int PS4_SYSV_ABI sceAudioOutGetSystemState();
-int PS4_SYSV_ABI sceAudioOutInit();
-int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
-int PS4_SYSV_ABI sceAudioOutMasteringGetState();
-int PS4_SYSV_ABI sceAudioOutMasteringInit();
-int PS4_SYSV_ABI sceAudioOutMasteringSetParam();
-int PS4_SYSV_ABI sceAudioOutMasteringTerm();
-int PS4_SYSV_ABI sceAudioOutMbusInit();
+s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
+s32 PS4_SYSV_ABI sceAudioDeviceControlGet();
+s32 PS4_SYSV_ABI sceAudioDeviceControlSet();
+s32 PS4_SYSV_ABI sceAudioOutA3dControl();
+s32 PS4_SYSV_ABI sceAudioOutA3dExit();
+s32 PS4_SYSV_ABI sceAudioOutA3dInit();
+s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
+s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
+s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle);
+s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
+s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
+s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
+s32 PS4_SYSV_ABI sceAudioOutExPtClose();
+s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
+s32 PS4_SYSV_ABI sceAudioOutExPtOpen();
+s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
+s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
+s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
+s32 PS4_SYSV_ABI sceAudioOutGetInfo();
+s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
+s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
+s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
+s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
+s32 PS4_SYSV_ABI sceAudioOutGetSparkVss();
+s32 PS4_SYSV_ABI sceAudioOutGetSystemState();
+s32 PS4_SYSV_ABI sceAudioOutInit();
+s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
+s32 PS4_SYSV_ABI sceAudioOutMasteringGetState();
+s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags);
+s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam();
+s32 PS4_SYSV_ABI sceAudioOutMasteringTerm();
+s32 PS4_SYSV_ABI sceAudioOutMbusInit();
s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
OrbisAudioOutPort port_type, s32 index, u32 length,
u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type);
-int PS4_SYSV_ABI sceAudioOutOpenEx();
+s32 PS4_SYSV_ABI sceAudioOutOpenEx();
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr);
s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num);
-int PS4_SYSV_ABI sceAudioOutPtClose();
-int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
-int PS4_SYSV_ABI sceAudioOutPtOpen();
-int PS4_SYSV_ABI sceAudioOutSetConnections();
-int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
-int PS4_SYSV_ABI sceAudioOutSetDevConnection();
-int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
-int PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
-int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
-int PS4_SYSV_ABI sceAudioOutSetMainOutput();
-int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk();
-int PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
-int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
-int PS4_SYSV_ABI sceAudioOutSetPortConnections();
-int PS4_SYSV_ABI sceAudioOutSetPortStatuses();
-int PS4_SYSV_ABI sceAudioOutSetRecMode();
-int PS4_SYSV_ABI sceAudioOutSetSparkParam();
-int PS4_SYSV_ABI sceAudioOutSetUsbVolume();
+s32 PS4_SYSV_ABI sceAudioOutPtClose();
+s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
+s32 PS4_SYSV_ABI sceAudioOutPtOpen();
+s32 PS4_SYSV_ABI sceAudioOutSetConnections();
+s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
+s32 PS4_SYSV_ABI sceAudioOutSetDevConnection();
+s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
+s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
+s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
+s32 PS4_SYSV_ABI sceAudioOutSetMainOutput();
+s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel);
+s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
+s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
+s32 PS4_SYSV_ABI sceAudioOutSetPortConnections();
+s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses();
+s32 PS4_SYSV_ABI sceAudioOutSetRecMode();
+s32 PS4_SYSV_ABI sceAudioOutSetSparkParam();
+s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume();
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol);
-int PS4_SYSV_ABI sceAudioOutSetVolumeDown();
-int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
-int PS4_SYSV_ABI sceAudioOutStartSharePlay();
-int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
-int PS4_SYSV_ABI sceAudioOutStopSharePlay();
-int PS4_SYSV_ABI sceAudioOutSuspendResume();
-int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
-int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
-int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
-int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
-int PS4_SYSV_ABI sceAudioOutSystemControlGet();
-int PS4_SYSV_ABI sceAudioOutSystemControlSet();
-int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
-int PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
+s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown();
+s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
+s32 PS4_SYSV_ABI sceAudioOutStartSharePlay();
+s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
+s32 PS4_SYSV_ABI sceAudioOutStopSharePlay();
+s32 PS4_SYSV_ABI sceAudioOutSuspendResume();
+s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
+s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
+s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
+s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
+s32 PS4_SYSV_ABI sceAudioOutSystemControlGet();
+s32 PS4_SYSV_ABI sceAudioOutSystemControlSet();
+s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
+s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
void AdjustVol();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp
deleted file mode 100644
index 46dd33d73..000000000
--- a/src/core/libraries/audio/sdl_audio.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include
-#include
-#include
-#include
-
-#include "common/config.h"
-#include "common/logging/log.h"
-#include "core/libraries/audio/audioout.h"
-#include "core/libraries/audio/audioout_backend.h"
-
-#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro
-namespace Libraries::AudioOut {
-
-class SDLPortBackend : public PortBackend {
-public:
- explicit SDLPortBackend(const PortOut& port)
- : frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
- const SDL_AudioSpec fmt = {
- .format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
- .channels = port.format_info.num_channels,
- .freq = static_cast(port.sample_rate),
- };
-
- // Determine port type
- std::string port_name = port.type == OrbisAudioOutPort::PadSpk
- ? Config::getPadSpkOutputDevice()
- : Config::getMainOutputDevice();
- SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
- if (port_name == "None") {
- stream = nullptr;
- return;
- } else if (port_name == "Default Device") {
- dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
- } else {
- try {
- SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr);
- for (; dev_array != 0;) {
- std::string dev_name(SDL_GetAudioDeviceName(*dev_array));
- if (dev_name == port_name) {
- dev_id = *dev_array;
- break;
- } else {
- dev_array++;
- }
- }
- if (dev_id == SDL_INVALID_AUDIODEVICEID) {
- LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name);
- dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
- }
- } catch (const std::exception& e) {
- LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name);
- stream = nullptr;
- return;
- }
- }
-
- // Open the audio stream
- stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
- if (stream == nullptr) {
- LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
- return;
- }
- CalculateQueueThreshold();
- if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
- port.format_info.num_channels)) {
- LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
- SDL_GetError());
- SDL_DestroyAudioStream(stream);
- stream = nullptr;
- return;
- }
- if (!SDL_ResumeAudioStreamDevice(stream)) {
- LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
- SDL_DestroyAudioStream(stream);
- stream = nullptr;
- return;
- }
- SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f);
- }
-
- ~SDLPortBackend() override {
- if (!stream) {
- return;
- }
- SDL_DestroyAudioStream(stream);
- stream = nullptr;
- }
-
- void Output(void* ptr) override {
- if (!stream) {
- return;
- }
- // AudioOut library manages timing, but we still need to guard against the SDL
- // audio queue stalling, which may happen during device changes, for example.
- // Otherwise, latency may grow over time unbounded.
- if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
- LOG_INFO(Lib_AudioOut, "SDL audio queue backed up ({} queued, {} threshold), clearing.",
- queued, queue_threshold);
- SDL_ClearAudioStream(stream);
- // Recalculate the threshold in case this happened because of a device change.
- CalculateQueueThreshold();
- }
- if (!SDL_PutAudioStreamData(stream, ptr, static_cast(guest_buffer_size))) {
- LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
- }
- }
-
- void SetVolume(const std::array& ch_volumes) override {
- if (!stream) {
- return;
- }
- // SDL does not have per-channel volumes, for now just take the maximum of the channels.
- const auto vol = *std::ranges::max_element(ch_volumes);
- if (!SDL_SetAudioStreamGain(stream, static_cast(vol) / SCE_AUDIO_OUT_VOLUME_0DB *
- Config::getVolumeSlider() / 100.0f)) {
- LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
- SDL_GetError());
- }
- }
-
-private:
- void CalculateQueueThreshold() {
- SDL_AudioSpec discard;
- int sdl_buffer_frames;
- if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
- &sdl_buffer_frames)) {
- LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}",
- SDL_GetError());
- sdl_buffer_frames = 0;
- }
- const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
- const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
- if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
- host_buffer_size = sdl_buffer_size;
- queue_threshold = new_threshold;
- LOG_INFO(Lib_AudioOut,
- "SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
- guest_buffer_size, host_buffer_size, queue_threshold);
- }
- }
-
- u32 frame_size;
- u32 guest_buffer_size;
- u32 host_buffer_size{};
- u32 queue_threshold{};
- SDL_AudioStream* stream{};
-};
-
-std::unique_ptr SDLAudioOut::Open(PortOut& port) {
- return std::make_unique(port);
-}
-
-} // namespace Libraries::AudioOut
diff --git a/src/core/libraries/audio/sdl_audio_in.cpp b/src/core/libraries/audio/sdl_audio_in.cpp
new file mode 100644
index 000000000..d36811175
--- /dev/null
+++ b/src/core/libraries/audio/sdl_audio_in.cpp
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include
+#include
+#include
+#include "audioin.h"
+#include "audioin_backend.h"
+
+namespace Libraries::AudioIn {
+
+class SDLInPortBackend : public PortInBackend {
+public:
+ explicit SDLInPortBackend(const PortIn& port) : port(port) {
+ SDL_AudioFormat sampleFormat = SDL_AUDIO_S16; // PS4 uses S16 format
+
+ SDL_AudioSpec fmt;
+ SDL_zero(fmt);
+ fmt.format = sampleFormat;
+ fmt.channels = static_cast(port.channels_num);
+ fmt.freq = static_cast(port.freq);
+
+ std::string micDevStr = Config::getMicDevice();
+ uint32_t devId = 0;
+ if (micDevStr == "None") {
+ nullDevice = true;
+ LOG_INFO(Lib_AudioIn, "Audio input disabled by configuration");
+ } else if (micDevStr == "Default Device") {
+ devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
+ LOG_INFO(Lib_AudioIn, "Using default audio input device");
+ } else {
+ try {
+ devId = static_cast(std::stoul(micDevStr));
+ LOG_INFO(Lib_AudioIn, "Using audio input device ID: {}", devId);
+ } catch (const std::exception& e) {
+ nullDevice = true;
+ LOG_WARNING(Lib_AudioIn, "Invalid device ID '{}', disabling input", micDevStr);
+ }
+ }
+
+ if (!nullDevice) {
+ stream = SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
+ if (stream) {
+ if (SDL_ResumeAudioStreamDevice(stream)) {
+ LOG_INFO(Lib_AudioIn, "Audio input opened: {} Hz, {} channels, format {}",
+ port.freq, port.channels_num, static_cast(port.format));
+ } else {
+ SDL_DestroyAudioStream(stream);
+ stream = nullptr;
+ LOG_ERROR(Lib_AudioIn, "Failed to resume audio input stream");
+ }
+ } else {
+ LOG_ERROR(Lib_AudioIn, "Failed to open audio input device: {}", SDL_GetError());
+ }
+ }
+
+ // Allocate internal buffer for null device simulation
+ if (!stream) {
+ const size_t bufferSize = port.samples_num * port.sample_size * port.channels_num;
+ internal_buffer = std::malloc(bufferSize);
+ if (internal_buffer) {
+ // Fill with silence
+ std::memset(internal_buffer, 0, bufferSize);
+ LOG_INFO(Lib_AudioIn, "Created null audio input buffer of {} bytes", bufferSize);
+ }
+ }
+ }
+
+ ~SDLInPortBackend() override {
+ if (stream) {
+ SDL_DestroyAudioStream(stream);
+ }
+ if (internal_buffer) {
+ std::free(internal_buffer);
+ internal_buffer = nullptr;
+ }
+ }
+
+ int Read(void* out_buffer) override {
+ const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
+
+ if (stream) {
+ // Read from actual audio device
+ int attempts = 0;
+ while (SDL_GetAudioStreamAvailable(stream) < bytesToRead) {
+ SDL_Delay(1);
+ if (++attempts > 1000) {
+ return 0;
+ }
+ }
+
+ const int bytesRead = SDL_GetAudioStreamData(stream, out_buffer, bytesToRead);
+ if (bytesRead < 0) {
+ LOG_ERROR(Lib_AudioIn, "Audio input read error: {}", SDL_GetError());
+ return 0;
+ }
+
+ const int framesRead = bytesRead / (port.sample_size * port.channels_num);
+ return framesRead;
+ } else if (internal_buffer) {
+ // Return silence from null device buffer
+ std::memcpy(out_buffer, internal_buffer, bytesToRead);
+ return port.samples_num;
+ } else {
+ // No device available
+ return 0;
+ }
+ }
+
+ void Clear() override {
+ if (stream) {
+ SDL_ClearAudioStream(stream);
+ }
+ }
+ bool IsAvailable() override {
+ if (nullDevice) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+private:
+ const PortIn& port;
+ SDL_AudioStream* stream = nullptr;
+ void* internal_buffer = nullptr;
+ bool nullDevice = false;
+};
+
+std::unique_ptr SDLAudioIn::Open(PortIn& port) {
+ return std::make_unique(port);
+}
+
+} // namespace Libraries::AudioIn
\ No newline at end of file
diff --git a/src/core/libraries/audio/sdl_audio_out.cpp b/src/core/libraries/audio/sdl_audio_out.cpp
new file mode 100644
index 000000000..572525c85
--- /dev/null
+++ b/src/core/libraries/audio/sdl_audio_out.cpp
@@ -0,0 +1,455 @@
+// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "common/config.h"
+#include "common/logging/log.h"
+#include "core/libraries/audio/audioout.h"
+#include "core/libraries/audio/audioout_backend.h"
+#include "core/libraries/kernel/threads.h"
+
+#define SDL_INVALID_AUDIODEVICEID 0
+
+namespace Libraries::AudioOut {
+
+// Volume constants
+constexpr float VOLUME_0DB = 32768.0f; // 1 << 15
+
+// Channel positions
+enum ChannelPos {
+ FL = 0,
+ FR = 1,
+ FC = 2,
+ LF = 3,
+ SL = 4,
+ SR = 5,
+ BL = 6,
+ BR = 7,
+ STD_SL = 6,
+ STD_SR = 7,
+ STD_BL = 4,
+ STD_BR = 5
+};
+
+class SDLPortBackend : public PortBackend {
+public:
+ explicit SDLPortBackend(const PortOut& port)
+ : frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()),
+ buffer_frames(port.buffer_frames), sample_rate(port.sample_rate),
+ num_channels(port.format_info.num_channels), is_float(port.format_info.is_float),
+ is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout) {
+
+ // Calculate timing
+ period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate;
+ last_output_time = 0;
+ next_output_time = 0;
+
+ // Allocate internal buffer
+ internal_buffer_size = buffer_frames * sizeof(float) * num_channels;
+ internal_buffer = std::malloc(internal_buffer_size);
+ if (!internal_buffer) {
+ LOG_ERROR(Lib_AudioOut, "Failed to allocate internal audio buffer");
+ return;
+ }
+
+ // Initialize current gain
+ current_gain.store(Config::getVolumeSlider() / 100.0f);
+
+ // Select converter function
+ SelectConverter();
+
+ // Open SDL device
+ if (!OpenDevice(port.type)) {
+ std::free(internal_buffer);
+ internal_buffer = nullptr;
+ return;
+ }
+
+ CalculateQueueThreshold();
+ }
+
+ ~SDLPortBackend() override {
+ if (stream) {
+ SDL_DestroyAudioStream(stream);
+ }
+ if (internal_buffer) {
+ std::free(internal_buffer);
+ }
+ }
+
+ void Output(void* ptr) override {
+ if (!stream || !internal_buffer) {
+ return;
+ }
+
+ // Check for volume changes and update if needed
+ UpdateVolumeIfChanged();
+
+ // Get current time in microseconds
+ u64 current_time = Kernel::sceKernelGetProcessTime();
+
+ if (ptr != nullptr) {
+ // Simple format conversion (no volume application)
+ convert(ptr, internal_buffer, buffer_frames, nullptr);
+
+ if (next_output_time == 0) {
+ next_output_time = current_time + period_us;
+ } else if (current_time > next_output_time) {
+ next_output_time = current_time + period_us;
+ } else {
+ u64 wait_until = next_output_time;
+ next_output_time += period_us;
+
+ if (current_time < wait_until) {
+ u64 sleep_us = wait_until - current_time;
+ if (sleep_us > 10) {
+ sleep_us -= 10;
+ std::this_thread::sleep_for(std::chrono::microseconds(sleep_us));
+ }
+ }
+ }
+
+ last_output_time = current_time;
+
+ // Check queue and clear if backed up
+ if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
+ LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued,
+ queue_threshold);
+ SDL_ClearAudioStream(stream);
+ CalculateQueueThreshold();
+ }
+
+ if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) {
+ LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
+ }
+ }
+ }
+
+ void SetVolume(const std::array& ch_volumes) override {
+ if (!stream) {
+ return;
+ }
+ float max_channel_gain = 0.0f;
+ for (int i = 0; i < num_channels && i < 8; i++) {
+ float channel_gain = static_cast(ch_volumes[i]) / VOLUME_0DB;
+ max_channel_gain = std::max(max_channel_gain, channel_gain);
+ }
+
+ // Combine with global volume slider
+ float total_gain = max_channel_gain * (Config::getVolumeSlider() / 100.0f);
+
+ std::lock_guard lock(volume_mutex);
+ if (SDL_SetAudioStreamGain(stream, total_gain)) {
+ current_gain.store(total_gain);
+ LOG_DEBUG(Lib_AudioOut,
+ "Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})",
+ total_gain, max_channel_gain, Config::getVolumeSlider() / 100.0f);
+ } else {
+ LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
+ }
+ }
+
+ u64 GetLastOutputTime() const {
+ return last_output_time;
+ }
+
+private:
+ std::atomic volume_update_needed{false};
+ u64 last_volume_check_time{0};
+ static constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms
+
+ void UpdateVolumeIfChanged() {
+ u64 current_time = Kernel::sceKernelGetProcessTime();
+
+ // Only check volume every 50ms to reduce overhead
+ if (current_time - last_volume_check_time >= VOLUME_CHECK_INTERVAL_US) {
+ last_volume_check_time = current_time;
+
+ float config_volume = Config::getVolumeSlider() / 100.0f;
+ float stored_gain = current_gain.load();
+
+ if (std::abs(config_volume - stored_gain) > 0.001f) {
+ if (SDL_SetAudioStreamGain(stream, config_volume)) {
+ current_gain.store(config_volume);
+ LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume);
+ } else {
+ LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
+ }
+ }
+ }
+ }
+ bool OpenDevice(OrbisAudioOutPort type) {
+ const SDL_AudioSpec fmt = {
+ .format = SDL_AUDIO_F32LE, // Always use float for internal processing
+ .channels = static_cast(num_channels),
+ .freq = static_cast(sample_rate),
+ };
+
+ // Determine device name
+ std::string device_name = GetDeviceName(type);
+ SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
+
+ if (device_name == "None") {
+ LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}",
+ static_cast(type));
+ return false;
+ } else if (device_name.empty() || device_name == "Default Device") {
+ dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
+ } else {
+ int num_devices = 0;
+ SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices);
+
+ if (dev_array) {
+ bool found = false;
+ for (int i = 0; i < num_devices; i++) {
+ const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]);
+ if (dev_name && std::string(dev_name) == device_name) {
+ dev_id = dev_array[i];
+ found = true;
+ break;
+ }
+ }
+ SDL_free(dev_array);
+
+ if (!found) {
+ LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default",
+ device_name);
+ dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
+ }
+ } else {
+ LOG_WARNING(Lib_AudioOut, "No audio devices found, using default");
+ dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
+ }
+ }
+
+ // Create audio stream
+ stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
+ if (!stream) {
+ LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
+ return false;
+ }
+
+ // Set channel map
+ if (num_channels > 0) {
+ std::vector channel_map(num_channels);
+
+ if (is_std && num_channels == 8) {
+ // Standard 8CH layout
+ channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR};
+ } else {
+ // Use provided channel layout
+ for (int i = 0; i < num_channels; i++) {
+ channel_map[i] = channel_layout[i];
+ }
+ }
+
+ if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) {
+ LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError());
+ SDL_DestroyAudioStream(stream);
+ stream = nullptr;
+ return false;
+ }
+ }
+
+ // Set initial volume
+ float initial_gain = current_gain.load();
+ if (!SDL_SetAudioStreamGain(stream, initial_gain)) {
+ LOG_WARNING(Lib_AudioOut, "Failed to set initial audio gain: {}", SDL_GetError());
+ }
+
+ // Start playback
+ if (!SDL_ResumeAudioStreamDevice(stream)) {
+ LOG_ERROR(Lib_AudioOut, "Failed to resume audio stream: {}", SDL_GetError());
+ SDL_DestroyAudioStream(stream);
+ stream = nullptr;
+ return false;
+ }
+
+ LOG_INFO(Lib_AudioOut, "Opened audio device: {} ({} Hz, {} ch, gain: {:.3f})", device_name,
+ sample_rate, num_channels, initial_gain);
+ return true;
+ }
+
+ std::string GetDeviceName(OrbisAudioOutPort type) {
+ switch (type) {
+ case OrbisAudioOutPort::Main:
+ case OrbisAudioOutPort::Bgm:
+ return Config::getMainOutputDevice();
+ // case OrbisAudioOutPort::Voice:
+ // case OrbisAudioOutPort::Personal:
+ // return Config::getHeadphoneOutputDevice();
+ case OrbisAudioOutPort::PadSpk:
+ return Config::getPadSpkOutputDevice();
+ // case OrbisAudioOutPort::Aux:
+ // return Config::getSpecialOutputDevice();
+ default:
+ return Config::getMainOutputDevice();
+ }
+ }
+
+ void SelectConverter() {
+ if (is_float) {
+ switch (num_channels) {
+ case 1:
+ convert = &ConvertF32Mono;
+ break;
+ case 2:
+ convert = &ConvertF32Stereo;
+ break;
+ case 8:
+ if (is_std) {
+ convert = &ConvertF32Std8CH;
+ } else {
+ convert = &ConvertF32_8CH;
+ }
+ break;
+ default:
+ LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
+ convert = nullptr;
+ }
+ } else {
+ switch (num_channels) {
+ case 1:
+ convert = &ConvertS16Mono;
+ break;
+ case 2:
+ convert = &ConvertS16Stereo;
+ break;
+ case 8:
+ convert = &ConvertS16_8CH;
+ break;
+ default:
+ LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
+ convert = nullptr;
+ }
+ }
+ }
+
+ void CalculateQueueThreshold() {
+ if (!stream)
+ return;
+
+ SDL_AudioSpec discard;
+ int sdl_buffer_frames;
+ if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
+ &sdl_buffer_frames)) {
+ LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError());
+ sdl_buffer_frames = 0;
+ }
+
+ u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels;
+ queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
+
+ LOG_DEBUG(Lib_AudioOut, "Audio queue threshold: {} bytes (SDL buffer: {} frames)",
+ queue_threshold, sdl_buffer_frames);
+ }
+
+ using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes);
+
+ // Remove volume parameter and application from all converters
+ static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) {
+ const s16* s = static_cast(src);
+ float* d = static_cast(dst);
+
+ constexpr float inv_scale = 1.0f / VOLUME_0DB;
+
+ for (u32 i = 0; i < frames; i++) {
+ d[i] = s[i] * inv_scale;
+ }
+ }
+
+ static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) {
+ const s16* s = static_cast(src);
+ float* d = static_cast(dst);
+
+ constexpr float inv_scale = 1.0f / VOLUME_0DB;
+
+ for (u32 i = 0; i < frames; i++) {
+ d[i * 2] = s[i * 2] * inv_scale;
+ d[i * 2 + 1] = s[i * 2 + 1] * inv_scale;
+ }
+ }
+
+ static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) {
+ const s16* s = static_cast(src);
+ float* d = static_cast(dst);
+
+ constexpr float inv_scale = 1.0f / VOLUME_0DB;
+
+ for (u32 i = 0; i < frames; i++) {
+ for (int ch = 0; ch < 8; ch++) {
+ d[i * 8 + ch] = s[i * 8 + ch] * inv_scale;
+ }
+ }
+ }
+
+ // Float converters become simple memcpy or passthrough
+ static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) {
+ std::memcpy(dst, src, frames * sizeof(float));
+ }
+
+ static void ConvertF32Stereo(const void* src, void* dst, u32 frames, const float*) {
+ std::memcpy(dst, src, frames * 2 * sizeof(float));
+ }
+
+ static void ConvertF32_8CH(const void* src, void* dst, u32 frames, const float*) {
+ std::memcpy(dst, src, frames * 8 * sizeof(float));
+ }
+
+ static void ConvertF32Std8CH(const void* src, void* dst, u32 frames, const float*) {
+ const float* s = static_cast(src);
+ float* d = static_cast(dst);
+
+ for (u32 i = 0; i < frames; i++) {
+ d[i * 8 + FL] = s[i * 8 + FL];
+ d[i * 8 + FR] = s[i * 8 + FR];
+ d[i * 8 + FC] = s[i * 8 + FC];
+ d[i * 8 + LF] = s[i * 8 + LF];
+ d[i * 8 + SL] = s[i * 8 + STD_SL]; // Channel remapping still needed
+ d[i * 8 + SR] = s[i * 8 + STD_SR];
+ d[i * 8 + BL] = s[i * 8 + STD_BL];
+ d[i * 8 + BR] = s[i * 8 + STD_BR];
+ }
+ }
+
+ // Member variables
+ u32 frame_size;
+ u32 guest_buffer_size;
+ u32 buffer_frames;
+ u32 sample_rate;
+ u32 num_channels;
+ bool is_float;
+ bool is_std;
+ std::array channel_layout;
+
+ u64 period_us;
+ u64 last_output_time;
+ u64 next_output_time;
+
+ // Buffers
+ u32 internal_buffer_size;
+ void* internal_buffer;
+
+ // Converter function
+ ConverterFunc convert;
+
+ // Volume tracking
+ std::atomic current_gain{1.0f};
+ mutable std::mutex volume_mutex;
+
+ // SDL
+ SDL_AudioStream* stream{};
+ u32 queue_threshold{};
+};
+
+std::unique_ptr SDLAudioOut::Open(PortOut& port) {
+ return std::make_unique(port);
+}
+
+} // namespace Libraries::AudioOut
\ No newline at end of file
diff --git a/src/core/libraries/audio/sdl_in.cpp b/src/core/libraries/audio/sdl_in.cpp
deleted file mode 100644
index 30bc0c578..000000000
--- a/src/core/libraries/audio/sdl_in.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include
-#include
-#include
-#include "sdl_in.h"
-
-int SDLAudioIn::AudioInit() {
- return SDL_InitSubSystem(SDL_INIT_AUDIO);
-}
-
-int SDLAudioIn::AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format) {
- std::scoped_lock lock{m_mutex};
-
- for (int id = 0; id < static_cast(portsIn.size()); ++id) {
- auto& port = portsIn[id];
- if (!port.isOpen) {
- port.isOpen = true;
- port.type = type;
- port.samples_num = samples_num;
- port.freq = freq;
- port.format = format;
-
- SDL_AudioFormat sampleFormat;
- switch (format) {
- case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO:
- sampleFormat = SDL_AUDIO_S16;
- port.channels_num = 1;
- port.sample_size = 2;
- break;
- case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO:
- sampleFormat = SDL_AUDIO_S16;
- port.channels_num = 2;
- port.sample_size = 2;
- break;
- default:
- port.isOpen = false;
- return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
- }
-
- SDL_AudioSpec fmt;
- SDL_zero(fmt);
- fmt.format = sampleFormat;
- fmt.channels = port.channels_num;
- fmt.freq = port.freq;
-
- std::string micDevStr = Config::getMicDevice();
- uint32_t devId;
-
- bool nullDevice = false;
- if (micDevStr == "None") {
- nullDevice = true;
- } else if (micDevStr == "Default Device") {
- devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
- } else {
- try {
- devId = static_cast(std::stoul(micDevStr));
- } catch (const std::exception& e) {
- nullDevice = true;
- }
- }
-
- port.stream =
- nullDevice ? nullptr : SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
-
- if (!port.stream) {
- // if stream is null, either due to configuration disabling the input,
- // or no input devices present in the system, still return a valid id
- // as some games require that (e.g. L.A. Noire)
- return id + 1;
- }
-
- if (SDL_ResumeAudioStreamDevice(port.stream) == false) {
- SDL_DestroyAudioStream(port.stream);
- port = {};
- return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
- }
-
- return id + 1;
- }
- }
-
- return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
-}
-
-int SDLAudioIn::AudioInInput(int handle, void* out_buffer) {
- std::scoped_lock lock{m_mutex};
-
- if (handle < 1 || handle > static_cast(portsIn.size()) || !out_buffer)
- return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
-
- auto& port = portsIn[handle - 1];
- if (!port.isOpen)
- return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
-
- const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
-
- if (out_buffer == nullptr) {
- int attempts = 0;
- while (SDL_GetAudioStreamAvailable(port.stream) > 0) {
- SDL_Delay(1);
- if (++attempts > 1000) {
- return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
- }
- }
- return 0; // done
- }
-
- int attempts = 0;
- while (SDL_GetAudioStreamAvailable(port.stream) < bytesToRead) {
- SDL_Delay(1);
- if (++attempts > 1000) {
- return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
- }
- }
-
- const int bytesRead = SDL_GetAudioStreamData(port.stream, out_buffer, bytesToRead);
- if (bytesRead < 0) {
- // SDL_GetAudioStreamData failed
- LOG_ERROR(Lib_AudioIn, "AudioInInput error: {}", SDL_GetError());
- return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
- }
- const int framesRead = bytesRead / (port.sample_size * port.channels_num);
- return framesRead;
-}
-
-void SDLAudioIn::AudioInClose(int handle) {
- std::scoped_lock lock{m_mutex};
- if (handle < 1 || handle > (int)portsIn.size())
- return;
-
- auto& port = portsIn[handle - 1];
- if (!port.isOpen)
- return;
-
- SDL_DestroyAudioStream(port.stream);
- port = {};
-}
\ No newline at end of file
diff --git a/src/core/libraries/audio/sdl_in.h b/src/core/libraries/audio/sdl_in.h
deleted file mode 100644
index e1b2a4682..000000000
--- a/src/core/libraries/audio/sdl_in.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include
-#include
-#include
-
-namespace Libraries::AudioIn {
-enum OrbisAudioInParam {
- ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO = 0,
- ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO = 2
-};
-}
-
-#define ORBIS_AUDIO_IN_ERROR_INVALID_PORT -1
-#define ORBIS_AUDIO_IN_ERROR_TIMEOUT -2
-#define ORBIS_AUDIO_IN_ERROR_STREAM_FAIL -3
-
-class SDLAudioIn {
-public:
- int AudioInit();
- int AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format);
- int AudioInInput(int handle, void* out_buffer);
- void AudioInClose(int handle);
-
-private:
- struct AudioInPort {
- bool isOpen = false;
- int type = 0;
- uint32_t samples_num = 0;
- uint32_t freq = 0;
- int channels_num = 0;
- int sample_size = 0;
- uint32_t format = 0;
- SDL_AudioStream* stream = nullptr;
- };
-
- std::array portsIn;
- std::mutex m_mutex;
-};
diff --git a/src/core/libraries/companion/companion_util.cpp b/src/core/libraries/companion/companion_util.cpp
index 758cfface..2794c49af 100644
--- a/src/core/libraries/companion/companion_util.cpp
+++ b/src/core/libraries/companion/companion_util.cpp
@@ -29,10 +29,9 @@ u32 PS4_SYSV_ABI getEvent(sceCompanionUtilContext* ctx, sceCompanionUtilEvent* o
}
s32 PS4_SYSV_ABI sceCompanionUtilGetEvent(sceCompanionUtilEvent* outEvent) {
- sceCompanionUtilContext* ctx = nullptr;
- u32 ret = getEvent(ctx, outEvent, 1);
+ u32 ret = ORBIS_COMPANION_UTIL_NO_EVENT;
- LOG_DEBUG(Lib_CompanionUtil, "(STUBBED) called ret: {}", ret);
+ LOG_DEBUG(Lib_CompanionUtil, "(STUBBED) called ret: {:#x}", ret);
return ret;
}
diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp
index 4b5a53266..bc4e2def6 100644
--- a/src/core/libraries/kernel/file_system.cpp
+++ b/src/core/libraries/kernel/file_system.cpp
@@ -34,6 +34,7 @@
#include
#else
#include
+#include
#endif
namespace D = Core::Devices;
@@ -751,6 +752,30 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
sb->st_size = file->f.GetSize();
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
+#if defined(__linux__) || defined(__FreeBSD__)
+ struct stat filestat = {};
+ stat(file->f.GetPath().c_str(), &filestat);
+ sb->st_atim = *reinterpret_cast(&filestat.st_atim);
+ sb->st_mtim = *reinterpret_cast(&filestat.st_mtim);
+ sb->st_ctim = *reinterpret_cast(&filestat.st_ctim);
+#elif defined(__APPLE__)
+ struct stat filestat = {};
+ stat(file->f.GetPath().c_str(), &filestat);
+ sb->st_atim = *reinterpret_cast(&filestat.st_atimespec);
+ sb->st_mtim = *reinterpret_cast(&filestat.st_mtimespec);
+ sb->st_ctim = *reinterpret_cast(&filestat.st_ctimespec);
+#else
+ const auto ft = std::filesystem::last_write_time(file->f.GetPath());
+ const auto sctp = std::chrono::time_point_cast(
+ ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
+ const auto secs = std::chrono::time_point_cast(sctp);
+ const auto nsecs = std::chrono::duration_cast(sctp - secs);
+
+ sb->st_mtim.tv_sec = static_cast(secs.time_since_epoch().count());
+ sb->st_mtim.tv_nsec = static_cast(nsecs.count());
+ sb->st_atim = sb->st_mtim;
+ sb->st_ctim = sb->st_mtim;
+#endif
// TODO incomplete
break;
}
diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp
index 5d97c5dc1..7a046e973 100644
--- a/src/core/libraries/kernel/threads/mutex.cpp
+++ b/src/core/libraries/kernel/threads/mutex.cpp
@@ -378,7 +378,8 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) {
}
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
- if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) {
+ if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck ||
+ type >= PthreadMutexType::Max) {
return POSIX_EINVAL;
}
(*attr)->m_type = type;
diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp
index f1107ef30..20bd20f4b 100644
--- a/src/core/libraries/kernel/threads/pthread.cpp
+++ b/src/core/libraries/kernel/threads/pthread.cpp
@@ -14,12 +14,6 @@
namespace Libraries::Kernel {
-constexpr int PthreadInheritSched = 4;
-
-constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
-constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
-constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
-
extern PthreadAttr PthreadAttrDefault;
void _thread_cleanupspecific();
@@ -231,7 +225,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
new_thread->attr = *(*attr);
new_thread->attr.cpusetsize = 0;
}
- if (new_thread->attr.sched_inherit == PthreadInheritSched) {
+ if (curthread != nullptr && new_thread->attr.sched_inherit == PthreadInheritSched) {
if (True(curthread->attr.flags & PthreadAttrFlags::ScopeSystem)) {
new_thread->attr.flags |= PthreadAttrFlags::ScopeSystem;
} else {
diff --git a/src/core/libraries/kernel/threads/pthread.h b/src/core/libraries/kernel/threads/pthread.h
index 17e5eddc5..b25723fde 100644
--- a/src/core/libraries/kernel/threads/pthread.h
+++ b/src/core/libraries/kernel/threads/pthread.h
@@ -24,6 +24,12 @@ class SymbolsResolver;
namespace Libraries::Kernel {
+constexpr int PthreadInheritSched = 4;
+
+constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
+constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
+constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
+
struct Pthread;
enum class PthreadMutexFlags : u32 {
diff --git a/src/core/libraries/kernel/threads/pthread_attr.cpp b/src/core/libraries/kernel/threads/pthread_attr.cpp
index 4fe9e72f2..aa5781f4f 100644
--- a/src/core/libraries/kernel/threads/pthread_attr.cpp
+++ b/src/core/libraries/kernel/threads/pthread_attr.cpp
@@ -24,9 +24,9 @@ static constexpr std::array ThrPriorities = {{
}};
PthreadAttr PthreadAttrDefault = {
- .sched_policy = SchedPolicy::Fifo,
- .sched_inherit = 0,
- .prio = 0,
+ .sched_policy = SchedPolicy::Other,
+ .sched_inherit = PthreadInheritSched,
+ .prio = ORBIS_KERNEL_PRIO_FIFO_DEFAULT,
.suspend = false,
.flags = PthreadAttrFlags::ScopeSystem,
.stackaddr_attr = nullptr,
diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp
index ebb10db68..8bc9b51f0 100644
--- a/src/core/libraries/network/http.cpp
+++ b/src/core/libraries/network/http.cpp
@@ -430,8 +430,8 @@ int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int
return index + 1;
}
-int PS4_SYSV_ABI sceHttpReadData() {
- LOG_ERROR(Lib_Http, "(STUBBED) called");
+int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size) {
+ LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size);
return ORBIS_OK;
}
diff --git a/src/core/libraries/network/http.h b/src/core/libraries/network/http.h
index 2ad5e171f..d373fd290 100644
--- a/src/core/libraries/network/http.h
+++ b/src/core/libraries/network/http.h
@@ -91,7 +91,7 @@ int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, c
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 sceHttpReadData();
+int PS4_SYSV_ABI sceHttpReadData(s32 reqId, void* data, u64 size);
int PS4_SYSV_ABI sceHttpRedirectCacheFlush();
int PS4_SYSV_ABI sceHttpRemoveRequestHeader();
int PS4_SYSV_ABI sceHttpRequestGetAllHeaders();
diff --git a/src/core/libraries/np/np_auth.cpp b/src/core/libraries/np/np_auth.cpp
index 0c855546c..b6091723c 100644
--- a/src/core/libraries/np/np_auth.cpp
+++ b/src/core/libraries/np/np_auth.cpp
@@ -123,7 +123,9 @@ s32 GetAuthorizationCode(s32 req_id, const OrbisNpAuthGetAuthorizationCodeParame
// Not sure what values are expected here, so zeroing these for now.
std::memset(auth_code, 0, sizeof(OrbisNpAuthorizationCode));
- *issuer_id = 0;
+ if (issuer_id != nullptr) {
+ *issuer_id = 0;
+ }
return ORBIS_OK;
}
diff --git a/src/core/libraries/np/np_common.h b/src/core/libraries/np/np_common.h
index 2fd4ecd7c..a130f9c1d 100644
--- a/src/core/libraries/np/np_common.h
+++ b/src/core/libraries/np/np_common.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/core/libraries/np/np_matching2.cpp b/src/core/libraries/np/np_matching2.cpp
index cf4faea39..423b84257 100644
--- a/src/core/libraries/np/np_matching2.cpp
+++ b/src/core/libraries/np/np_matching2.cpp
@@ -234,7 +234,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomMemberDataInternalA me{
nullptr,
0,
- {0xace104e, Libraries::Np::OrbisNpPlatformType::ORBIS_NP_PLATFORM_TYPE_PS4},
+ {0xace104e, Libraries::Np::OrbisNpPlatformType::PS4},
onlineId,
{0, 0, 0, 0},
1,
diff --git a/src/core/libraries/np/np_types.h b/src/core/libraries/np/np_types.h
index cc37b5a3d..58c119bec 100644
--- a/src/core/libraries/np/np_types.h
+++ b/src/core/libraries/np/np_types.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -46,18 +46,20 @@ struct OrbisNpIdToken {
};
using OrbisNpServiceLabel = u32;
+constexpr s32 ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF;
-enum class OrbisNpPlatformType : s32 {
- ORBIS_NP_PLATFORM_TYPE_NONE = 0,
- ORBIS_NP_PLATFORM_TYPE_PS3 = 1,
- ORBIS_NP_PLATFORM_TYPE_VITA = 2,
- ORBIS_NP_PLATFORM_TYPE_PS4 = 3,
+using OrbisNpAccountId = u64;
+enum OrbisNpPlatformType : s32 {
+ None = 0,
+ PS3 = 1,
+ Vita = 2,
+ PS4 = 3,
};
struct OrbisNpPeerAddressA {
OrbisNpAccountId accountId;
- OrbisNpPlatformType platformType;
- u8 padding[4];
+ OrbisNpPlatformType platform;
+ char padding[4];
};
}; // namespace Libraries::Np
\ No newline at end of file
diff --git a/src/core/libraries/np/np_web_api.cpp b/src/core/libraries/np/np_web_api.cpp
index db9d2f42a..e51b79a3c 100644
--- a/src/core/libraries/np/np_web_api.cpp
+++ b/src/core/libraries/np/np_web_api.cpp
@@ -1,155 +1,293 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_web_api.h"
+#include "core/libraries/np/np_web_api_error.h"
+#include "core/libraries/np/np_web_api_internal.h"
+
+#include
namespace Libraries::Np::NpWebApi {
-s32 PS4_SYSV_ABI sceNpWebApiCreateContext() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
+static bool g_is_initialized = false;
+static s32 g_active_library_contexts = 0;
+
+s32 PS4_SYSV_ABI sceNpWebApiCreateContext(s32 libCtxId, OrbisNpOnlineId* onlineId) {
+ if (libCtxId >= 0x8000) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID;
+ }
+ if (onlineId == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ return createUserContextWithOnlineId(libCtxId, onlineId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter(
+ s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam, u64 filterParamNum) {
+ if (pFilterParam == nullptr || filterParamNum == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_WARNING(Lib_NpWebApi, "called, libCtxId = {:#x}", libCtxId);
+ return createPushEventFilter(libCtxId, pFilterParam, filterParamNum);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter(
+ s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) {
+ if (pNpServiceName == nullptr || pFilterParam == nullptr || filterParamNum == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ if (getCompiledSdkVersion() >= Common::ElfInfo::FW_20 &&
+ npServiceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_WARNING(Lib_NpWebApi,
+ "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', "
+ "npServiceLabel = {:#x}",
+ libCtxId, handleId, pNpServiceName, npServiceLabel);
+ return createServicePushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel,
+ pFilterParam, filterParamNum);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter(s32 libCtxId, s32 filterId) {
+ LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}");
+ return deletePushEventFilter(libCtxId, filterId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter(s32 libCtxId, s32 filterId) {
+ LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}");
+ return deleteServicePushEventFilter(libCtxId, filterId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiExtdPushEventCallback cbFunc,
+ void* pUserArg) {
+ if (cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}",
+ titleUserCtxId, filterId, fmt::ptr(cbFunc));
+ return registerExtdPushEventCallback(titleUserCtxId, filterId, cbFunc, nullptr, pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback(s32 titleUserCtxId,
+ OrbisNpWebApiNotificationCallback cbFunc,
+ void* pUserArg) {
+ if (cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId,
+ fmt::ptr(cbFunc));
+ return registerNotificationCallback(titleUserCtxId, cbFunc, pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiPushEventCallback cbFunc,
+ void* pUserArg) {
+ if (getCompiledSdkVersion() >= Common::ElfInfo::FW_10 && cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}",
+ titleUserCtxId, filterId, fmt::ptr(cbFunc));
+ return registerPushEventCallback(titleUserCtxId, filterId, cbFunc, pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback(
+ s32 titleUserCtxId, s32 filterId, OrbisNpWebApiServicePushEventCallback cbFunc,
+ void* pUserArg) {
+ if (getCompiledSdkVersion() >= Common::ElfInfo::FW_10 && cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, filterId = {:#x}, cbFunc = {}",
+ titleUserCtxId, filterId, fmt::ptr(cbFunc));
+ return registerServicePushEventCallback(titleUserCtxId, filterId, cbFunc, nullptr, nullptr,
+ pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback(s32 titleUserCtxId) {
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}", titleUserCtxId);
+ return unregisterNotificationCallback(titleUserCtxId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId,
+ callbackId);
+ return unregisterPushEventCallback(titleUserCtxId, callbackId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId,
+ callbackId);
+ return unregisterServicePushEventCallback(titleUserCtxId, callbackId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiAbortHandle(s32 libCtxId, s32 handleId) {
+ LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId);
+ return abortHandle(libCtxId, handleId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiAbortRequest(s64 requestId) {
+ LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}", requestId);
+ return abortRequest(requestId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader(s64 requestId, const char* pFieldName,
+ const char* pValue) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : requestId = {:#x}, "
+ "pFieldName = '{}', pValue = '{}'",
+ requestId, (pFieldName ? pFieldName : "null"), (pValue ? pValue : "null"));
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart(s64 requestId,
+ const OrbisNpWebApiMultipartPartParameter* pParam,
+ s32* pIndex) {
+ LOG_INFO(Lib_NpWebApi,
+ "called (STUBBED) : requestId = {:#x}, "
+ "pParam = {}, pIndex = {}",
+ requestId, fmt::ptr(pParam), fmt::ptr(pIndex));
+ if (pParam) {
+ LOG_ERROR(Lib_NpWebApi, " Part params: headerNum = {}, contentLength = {}",
+ pParam->headerNum, pParam->contentLength);
+ }
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
+void PS4_SYSV_ABI sceNpWebApiCheckTimeout() {
+ LOG_TRACE(Lib_NpWebApi, "called");
+ if (!g_is_initialized) {
+ return;
+ }
+ return checkTimeout();
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection(s32 userCtxId,
+ bool bRemainKeepAliveConnection) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : userCtxId = {:#x}, "
+ "bRemainKeepAliveConnection = {}",
+ userCtxId, bRemainKeepAliveConnection);
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection(s32 userCtxId, const char* pApiGroup,
+ bool bRemainKeepAliveConnection) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : userCtxId = {:#x}, "
+ "pApiGroup = '{}', bRemainKeepAliveConnection = {}",
+ userCtxId, (pApiGroup ? pApiGroup : "null"), bRemainKeepAliveConnection);
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiCreateContextA(s32 libCtxId,
+ Libraries::UserService::OrbisUserServiceUserId userId) {
+ if (libCtxId >= 0x8000) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID;
+ }
+ if (userId == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ return createUserContext(libCtxId, userId);
}
-s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter(
+ s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum) {
+ if ((pNpServiceName != nullptr && npServiceLabel == ORBIS_NP_INVALID_SERVICE_LABEL) ||
+ pFilterParam == nullptr || filterParamNum == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(
+ Lib_NpWebApi,
+ "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', npServiceLabel = {:#x}",
+ libCtxId, handleId, (pNpServiceName ? pNpServiceName : "null"), npServiceLabel);
+ return createExtendedPushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel,
+ pFilterParam, filterParamNum, false);
}
-s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiCreateHandle(s32 libCtxId) {
+ return createHandle(libCtxId);
}
-s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest(s32 titleUserCtxId, const char* pApiGroup,
+ const char* pPath,
+ OrbisNpWebApiHttpMethod method,
+ s64* pRequestId) {
+ if (pApiGroup == nullptr || pPath == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (getCompiledSdkVersion() >= Common::ElfInfo::FW_25 &&
+ method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(Lib_NpWebApi,
+ "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
+ titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
+
+ return createRequest(titleUserCtxId, pApiGroup, pPath, method, nullptr, nullptr, pRequestId,
+ true);
}
-s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiCreateRequest(s32 titleUserCtxId, const char* pApiGroup,
+ const char* pPath, OrbisNpWebApiHttpMethod method,
+ const OrbisNpWebApiContentParameter* pContentParameter,
+ s64* pRequestId) {
+ if (pApiGroup == nullptr || pPath == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (pContentParameter != nullptr && pContentParameter->contentLength != 0 &&
+ pContentParameter->pContentType == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER;
+ }
+
+ if (getCompiledSdkVersion() >= Common::ElfInfo::FW_25 &&
+ method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_DELETE) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(Lib_NpWebApi,
+ "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
+ titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
+
+ return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, nullptr,
+ pRequestId, false);
}
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiDeleteContext(s32 titleUserCtxId) {
+ LOG_INFO(Lib_NpWebApi, "called titleUserCtxId = {:#x}", titleUserCtxId);
+ return deleteUserContext(titleUserCtxId);
}
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter(s32 libCtxId, s32 filterId) {
+ LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId);
+ return deleteExtendedPushEventFilter(libCtxId, filterId);
}
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle(s32 libCtxId, s32 handleId) {
+ LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId);
+ return deleteHandle(libCtxId, handleId);
}
-s32 PS4_SYSV_ABI sceNpWebApiAbortHandle() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest(s64 requestId) {
+ LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}", requestId);
+ return deleteRequest(requestId);
}
-s32 PS4_SYSV_ABI sceNpWebApiAbortRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCheckTimeout() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCreateContextA() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCreateHandle() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiCreateRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiDeleteContext() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
+s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats(s32 userCtxId, const char* pApiGroup,
+ OrbisNpWebApiConnectionStats* pStats) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : userCtxId = {:#x}, "
+ "pApiGroup = '{}', pStats = {}",
+ userCtxId, (pApiGroup ? pApiGroup : "null"), fmt::ptr(pStats));
return ORBIS_OK;
}
@@ -158,135 +296,300 @@ s32 PS4_SYSV_ABI sceNpWebApiGetErrorCode() {
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue() {
+s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue(s64 requestId, const char* pFieldName,
+ char* pValue, u64 valueSize) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : requestId = {:#x}, "
+ "pFieldName = '{}', pValue = {}, valueSize = {}",
+ requestId, (pFieldName ? pFieldName : "null"), fmt::ptr(pValue), valueSize);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength(s64 requestId, const char* pFieldName,
+ u64* pValueLength) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : requestId = {:#x}, "
+ "pFieldName = '{}', pValueLength = {}",
+ requestId, (pFieldName ? pFieldName : "null"), fmt::ptr(pValueLength));
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode(s64 requestId, s32* out_status_code) {
+ LOG_ERROR(Lib_NpWebApi, "called : requestId = {:#x}", requestId);
+ // On newer SDKs, NULL output pointer is invalid
+ if (getCompiledSdkVersion() > Common::ElfInfo::FW_10 && out_status_code == nullptr)
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ s32 returncode = getHttpStatusCodeInternal(requestId, out_status_code);
+ return returncode;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats(s32 libCtxId,
+ OrbisNpWebApiMemoryPoolStats* pCurrentStat) {
+ LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, pCurrentStat = {}", libCtxId,
+ fmt::ptr(pCurrentStat));
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiInitialize(s32 libHttpCtxId, u64 poolSize) {
+ LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
+ poolSize);
+ if (!g_is_initialized) {
+ g_is_initialized = true;
+ s32 result = initializeLibrary();
+ if (result < ORBIS_OK) {
+ return result;
+ }
+ }
+
+ s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 0);
+ if (result >= ORBIS_OK) {
+ g_active_library_contexts++;
+ }
+ return result;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence(s32 libHttpCtxId, u64 poolSize) {
+ LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
+ poolSize);
+ if (!g_is_initialized) {
+ g_is_initialized = true;
+ s32 result = initializeLibrary();
+ if (result < ORBIS_OK) {
+ return result;
+ }
+ }
+
+ s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 3);
+ if (result >= ORBIS_OK) {
+ g_active_library_contexts++;
+ }
+ return result;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter(
+ s32 libCtxId, s32 handleId, const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam,
+ u64 filterParamNum) {
+ if (pFilterParam == nullptr || filterParamNum == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, handleId = {:#x}", libCtxId, handleId);
+ return createExtendedPushEventFilter(libCtxId, handleId, nullptr,
+ ORBIS_NP_INVALID_SERVICE_LABEL, pFilterParam,
+ filterParamNum, true);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest(
+ s32 titleUserCtxId, const char* pApiGroup, const char* pPath, OrbisNpWebApiHttpMethod method,
+ const OrbisNpWebApiContentParameter* pContentParameter,
+ const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId) {
+ LOG_INFO(Lib_NpWebApi, "called");
+ if (pApiGroup == nullptr || pPath == nullptr ||
+ method > OrbisNpWebApiHttpMethod::ORBIS_NP_WEBAPI_HTTP_METHOD_PATCH) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (pContentParameter != nullptr && pContentParameter->contentLength != 0 &&
+ pContentParameter->pContentType == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER;
+ }
+
+ LOG_INFO(Lib_NpWebApi,
+ "called titleUserCtxId = {:#x}, pApiGroup = '{}', pPath = '{}', method = {}",
+ titleUserCtxId, pApiGroup, pPath, magic_enum::enum_name(method));
+
+ return createRequest(titleUserCtxId, pApiGroup, pPath, method, pContentParameter, pInternalArgs,
+ pRequestId, false);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter(
+ s32 libCtxId, s32 handleId, const char* pNpServiceName, OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) {
+ if (pFilterParam == nullptr || filterParamNum == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_WARNING(Lib_NpWebApi,
+ "called, libCtxId = {:#x}, handleId = {:#x}, pNpServiceName = '{}', "
+ "npServiceLabel = {:#x}",
+ libCtxId, handleId, (pNpServiceName ? pNpServiceName : "null"), npServiceLabel);
+ return createServicePushEventFilter(libCtxId, handleId, pNpServiceName, npServiceLabel,
+ pFilterParam, filterParamNum);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntInitialize(const OrbisNpWebApiIntInitializeArgs* args) {
+ LOG_INFO(Lib_NpWebApi, "called");
+ if (args == nullptr || args->structSize != sizeof(OrbisNpWebApiIntInitializeArgs)) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (!g_is_initialized) {
+ g_is_initialized = true;
+ s32 result = initializeLibrary();
+ if (result < ORBIS_OK) {
+ return result;
+ }
+ }
+
+ s32 result = createLibraryContext(args->libHttpCtxId, args->poolSize, args->name, 2);
+ if (result >= ORBIS_OK) {
+ g_active_library_contexts++;
+ }
+ return result;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback(
+ s32 titleUserCtxId, s32 filterId, OrbisNpWebApiInternalServicePushEventCallback cbFunc,
+ void* pUserArg) {
+ if (cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId,
+ fmt::ptr(cbFunc));
+ return registerServicePushEventCallback(titleUserCtxId, filterId, nullptr, cbFunc, nullptr,
+ pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA(
+ s32 titleUserCtxId, s32 filterId, OrbisNpWebApiInternalServicePushEventCallbackA cbFunc,
+ void* pUserArg) {
+ if (cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId,
+ fmt::ptr(cbFunc));
+ return registerServicePushEventCallback(titleUserCtxId, filterId, nullptr, nullptr, cbFunc,
+ pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiReadData(s64 requestId, void* pData, u64 size) {
+ LOG_ERROR(Lib_NpWebApi, "called : requestId = {:#x}, pData = {}, size = {:#x}", requestId,
+ fmt::ptr(pData), size);
+ if (pData == nullptr || size == 0)
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+
+ return readDataInternal(requestId, pData, size);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA(
+ s32 titleUserCtxId, s32 filterId, OrbisNpWebApiExtdPushEventCallbackA cbFunc, void* pUserArg) {
+ if (cbFunc == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, cbFunc = {}", titleUserCtxId,
+ fmt::ptr(cbFunc));
+ return registerExtdPushEventCallbackA(titleUserCtxId, filterId, cbFunc, pUserArg);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest(s64 requestId, s32 partIndex, const void* pData,
+ u64 dataSize) {
+ if (partIndex <= 0 || pData == nullptr || dataSize == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(Lib_NpWebApi,
+ "called, requestId = {:#x}, "
+ "partIndex = {:#x}, pData = {}, dataSize = {:#x}",
+ requestId, partIndex, fmt::ptr(pData), dataSize);
+ return sendRequest(requestId, partIndex, pData, dataSize, 0, nullptr);
+}
+
+s32 PS4_SYSV_ABI
+sceNpWebApiSendMultipartRequest2(s64 requestId, s32 partIndex, const void* pData, u64 dataSize,
+ OrbisNpWebApiResponseInformationOption* pRespInfoOption) {
+ if (partIndex <= 0 || pData == nullptr || dataSize == 0) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ LOG_INFO(Lib_NpWebApi,
+ "called, requestId = {:#x}, "
+ "partIndex = {:#x}, pData = {}, dataSize = {:#x}, pRespInfoOption = {}",
+ requestId, partIndex, fmt::ptr(pData), dataSize, fmt::ptr(pRespInfoOption));
+ return sendRequest(requestId, partIndex, pData, dataSize, 1, pRespInfoOption);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSendRequest(s64 requestId, const void* pData, u64 dataSize) {
+ LOG_INFO(Lib_NpWebApi, "called, requestId = {:#x}, pData = {}, dataSize = {:#x}", requestId,
+ fmt::ptr(pData), dataSize);
+ return sendRequest(requestId, 0, pData, dataSize, 0, nullptr);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSendRequest2(s64 requestId, const void* pData, u64 dataSize,
+ OrbisNpWebApiResponseInformationOption* pRespInfoOption) {
+ LOG_INFO(Lib_NpWebApi,
+ "called, requestId = {:#x}, "
+ "pData = {}, dataSize = {:#x}, pRespInfoOption = {}",
+ requestId, fmt::ptr(pData), dataSize, fmt::ptr(pRespInfoOption));
+ return sendRequest(requestId, 0, pData, dataSize, 1, pRespInfoOption);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout) {
+ LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, handleId = {:#x}, timeout = {} ms", libCtxId,
+ handleId, timeout);
+ return setHandleTimeout(libCtxId, handleId, timeout);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection(s32 libCtxId, s32 maxConnection) {
+ LOG_ERROR(Lib_NpWebApi, "called (STUBBED) : libCtxId = {:#x}, maxConnection = {}", libCtxId,
+ maxConnection);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType(s64 requestId, const char* pTypeName,
+ const char* pBoundary) {
+ LOG_ERROR(Lib_NpWebApi,
+ "called (STUBBED) : requestId = {:#x}, "
+ "pTypeName = '{}', pBoundary = '{}'",
+ requestId, (pTypeName ? pTypeName : "null"), (pBoundary ? pBoundary : "null"));
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout(s64 requestId, u32 timeout) {
+ LOG_INFO(Lib_NpWebApi, "called requestId = {:#x}, timeout = {} ms", requestId, timeout);
+ return setRequestTimeout(requestId, timeout);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiTerminate(s32 libCtxId) {
+ LOG_INFO(Lib_NpWebApi, "called libCtxId = {:#x}", libCtxId);
+ s32 result = terminateContext(libCtxId);
+ if (result != ORBIS_OK) {
+ return result;
+ }
+
+ g_active_library_contexts--;
+ if (g_active_library_contexts == 0) {
+ g_is_initialized = false;
+ }
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ LOG_INFO(Lib_NpWebApi, "called, titleUserCtxId = {:#x}, callbackId = {:#x}", titleUserCtxId,
+ callbackId);
+ return unregisterExtdPushEventCallback(titleUserCtxId, callbackId);
+}
+
+s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId(const char* pJsonNpId,
+ Libraries::Np::OrbisNpId* pNpId) {
LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
return ORBIS_OK;
}
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
+s32 PS4_SYSV_ABI sceNpWebApiVshInitialize(s32 libHttpCtxId, u64 poolSize) {
+ LOG_INFO(Lib_NpWebApi, "called libHttpCtxId = {:#x}, poolSize = {:#x} bytes", libHttpCtxId,
+ poolSize);
+ if (!g_is_initialized) {
+ g_is_initialized = true;
+ s32 result = initializeLibrary();
+ if (result < ORBIS_OK) {
+ return result;
+ }
+ }
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiInitialize() {
- LOG_ERROR(Lib_NpWebApi, "(DUMMY) called");
- static s32 id = 0;
- return ++id;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntInitialize() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiReadData() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest2() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSendRequest() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSendRequest2() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiTerminate() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
-}
-
-s32 PS4_SYSV_ABI sceNpWebApiVshInitialize() {
- LOG_ERROR(Lib_NpWebApi, "(STUBBED) called");
- return ORBIS_OK;
+ s32 result = createLibraryContext(libHttpCtxId, poolSize, nullptr, 4);
+ if (result >= ORBIS_OK) {
+ g_active_library_contexts++;
+ }
+ return result;
}
s32 PS4_SYSV_ABI Func_064C4ED1EDBEB9E8() {
diff --git a/src/core/libraries/np/np_web_api.h b/src/core/libraries/np/np_web_api.h
index 6679662cb..8dd9441e0 100644
--- a/src/core/libraries/np/np_web_api.h
+++ b/src/core/libraries/np/np_web_api.h
@@ -1,9 +1,12 @@
-// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// 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_common.h"
+#include "core/libraries/np/np_types.h"
+#include "core/libraries/system/userservice.h"
namespace Core::Loader {
class SymbolsResolver;
@@ -11,106 +14,115 @@ class SymbolsResolver;
namespace Libraries::Np::NpWebApi {
-s32 PS4_SYSV_ABI sceNpWebApiCreateContext();
-s32 PS4_SYSV_ABI sceNpWebApiCreatePushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiCreateServicePushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiDeletePushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiRegisterNotificationCallback();
-s32 PS4_SYSV_ABI sceNpWebApiRegisterPushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiRegisterServicePushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterNotificationCallback();
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterPushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterServicePushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiAbortHandle();
-s32 PS4_SYSV_ABI sceNpWebApiAbortRequest();
-s32 PS4_SYSV_ABI sceNpWebApiAddHttpRequestHeader();
-s32 PS4_SYSV_ABI sceNpWebApiAddMultipartPart();
-s32 PS4_SYSV_ABI sceNpWebApiCheckTimeout();
-s32 PS4_SYSV_ABI sceNpWebApiClearAllUnusedConnection();
-s32 PS4_SYSV_ABI sceNpWebApiClearUnusedConnection();
-s32 PS4_SYSV_ABI sceNpWebApiCreateContextA();
-s32 PS4_SYSV_ABI sceNpWebApiCreateExtdPushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiCreateHandle();
-s32 PS4_SYSV_ABI sceNpWebApiCreateMultipartRequest();
-s32 PS4_SYSV_ABI sceNpWebApiCreateRequest();
-s32 PS4_SYSV_ABI sceNpWebApiDeleteContext();
-s32 PS4_SYSV_ABI sceNpWebApiDeleteExtdPushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiDeleteHandle();
-s32 PS4_SYSV_ABI sceNpWebApiDeleteRequest();
-s32 PS4_SYSV_ABI sceNpWebApiGetConnectionStats();
-s32 PS4_SYSV_ABI sceNpWebApiGetErrorCode();
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValue();
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpResponseHeaderValueLength();
-s32 PS4_SYSV_ABI sceNpWebApiGetHttpStatusCode();
-s32 PS4_SYSV_ABI sceNpWebApiGetMemoryPoolStats();
-s32 PS4_SYSV_ABI sceNpWebApiInitialize();
-s32 PS4_SYSV_ABI sceNpWebApiInitializeForPresence();
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateCtxIndExtdPushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateRequest();
-s32 PS4_SYSV_ABI sceNpWebApiIntCreateServicePushEventFilter();
-s32 PS4_SYSV_ABI sceNpWebApiIntInitialize();
-s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiIntRegisterServicePushEventCallbackA();
-s32 PS4_SYSV_ABI sceNpWebApiReadData();
-s32 PS4_SYSV_ABI sceNpWebApiRegisterExtdPushEventCallbackA();
-s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest();
-s32 PS4_SYSV_ABI sceNpWebApiSendMultipartRequest2();
-s32 PS4_SYSV_ABI sceNpWebApiSendRequest();
-s32 PS4_SYSV_ABI sceNpWebApiSendRequest2();
-s32 PS4_SYSV_ABI sceNpWebApiSetHandleTimeout();
-s32 PS4_SYSV_ABI sceNpWebApiSetMaxConnection();
-s32 PS4_SYSV_ABI sceNpWebApiSetMultipartContentType();
-s32 PS4_SYSV_ABI sceNpWebApiSetRequestTimeout();
-s32 PS4_SYSV_ABI sceNpWebApiTerminate();
-s32 PS4_SYSV_ABI sceNpWebApiUnregisterExtdPushEventCallback();
-s32 PS4_SYSV_ABI sceNpWebApiUtilityParseNpId();
-s32 PS4_SYSV_ABI sceNpWebApiVshInitialize();
-s32 PS4_SYSV_ABI Func_064C4ED1EDBEB9E8();
-s32 PS4_SYSV_ABI Func_0783955D4E9563DA();
-s32 PS4_SYSV_ABI Func_1A6D77F3FD8323A8();
-s32 PS4_SYSV_ABI Func_1E0693A26FE0F954();
-s32 PS4_SYSV_ABI Func_24A9B5F1D77000CF();
-s32 PS4_SYSV_ABI Func_24AAA6F50E4C2361();
-s32 PS4_SYSV_ABI Func_24D8853D6B47FC79();
-s32 PS4_SYSV_ABI Func_279B3E9C7C4A9DC5();
-s32 PS4_SYSV_ABI Func_28461E29E9F8D697();
-s32 PS4_SYSV_ABI Func_3C29624704FAB9E0();
-s32 PS4_SYSV_ABI Func_3F027804ED2EC11E();
-s32 PS4_SYSV_ABI Func_4066C94E782997CD();
-s32 PS4_SYSV_ABI Func_47C85356815DBE90();
-s32 PS4_SYSV_ABI Func_4FCE8065437E3B87();
-s32 PS4_SYSV_ABI Func_536280BE3DABB521();
-s32 PS4_SYSV_ABI Func_57A0E1BC724219F3();
-s32 PS4_SYSV_ABI Func_5819749C040B6637();
-s32 PS4_SYSV_ABI Func_6198D0C825E86319();
-s32 PS4_SYSV_ABI Func_61F2B9E8AB093743();
-s32 PS4_SYSV_ABI Func_6BC388E6113F0D44();
-s32 PS4_SYSV_ABI Func_7500F0C4F8DC2D16();
-s32 PS4_SYSV_ABI Func_75A03814C7E9039F();
-s32 PS4_SYSV_ABI Func_789D6026C521416E();
-s32 PS4_SYSV_ABI Func_7DED63D06399EFFF();
-s32 PS4_SYSV_ABI Func_7E55A2DCC03D395A();
-s32 PS4_SYSV_ABI Func_7E6C8F9FB86967F4();
-s32 PS4_SYSV_ABI Func_7F04B7D4A7D41E80();
-s32 PS4_SYSV_ABI Func_8E167252DFA5C957();
-s32 PS4_SYSV_ABI Func_95D0046E504E3B09();
-s32 PS4_SYSV_ABI Func_97284BFDA4F18FDF();
-s32 PS4_SYSV_ABI Func_99E32C1F4737EAB4();
-s32 PS4_SYSV_ABI Func_9CFF661EA0BCBF83();
-s32 PS4_SYSV_ABI Func_9EB0E1F467AC3B29();
-s32 PS4_SYSV_ABI Func_A2318FE6FBABFAA3();
-s32 PS4_SYSV_ABI Func_BA07A2E1BF7B3971();
-s32 PS4_SYSV_ABI Func_BD0803EEE0CC29A0();
-s32 PS4_SYSV_ABI Func_BE6F4E5524BB135F();
-s32 PS4_SYSV_ABI Func_C0D490EB481EA4D0();
-s32 PS4_SYSV_ABI Func_C175D392CA6D084A();
-s32 PS4_SYSV_ABI Func_CD0136AF165D2F2F();
-s32 PS4_SYSV_ABI Func_D1C0ADB7B52FEAB5();
-s32 PS4_SYSV_ABI Func_E324765D18EE4D12();
-s32 PS4_SYSV_ABI Func_E789F980D907B653();
-s32 PS4_SYSV_ABI Func_F9A32E8685627436();
+#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
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpWebApi
\ No newline at end of file
diff --git a/src/core/libraries/np/np_web_api_error.h b/src/core/libraries/np/np_web_api_error.h
new file mode 100644
index 000000000..c7f08224f
--- /dev/null
+++ b/src/core/libraries/np/np_web_api_error.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+constexpr int ORBIS_NP_WEBAPI_ERROR_OUT_OF_MEMORY = 0x80552901;
+constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT = 0x80552902;
+constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = 0x80552903;
+constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80552904;
+constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = 0x80552905;
+constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = 0x80552906;
+constexpr int ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN = 0x80552907;
+constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = 0x80552908;
+constexpr int ORBIS_NP_WEBAPI_ERROR_ABORTED = 0x80552909;
+constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = 0x8055290a;
+constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290b;
+constexpr int ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290c;
+constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = 0x8055290d;
+constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055290e;
+constexpr int ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055290f;
+constexpr int ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = 0x80552910;
+constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY = 0x80552911;
+constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY = 0x80552912;
+constexpr int ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY = 0x80552913;
+constexpr int ORBIS_NP_WEBAPI_ERROR_INVALID_HTTP_STATUS_CODE = 0x80552914;
+constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_HTTP_HEADER = 0x80552915;
+constexpr int ORBIS_NP_WEBAPI_ERROR_PROHIBITED_FUNCTION_CALL = 0x80552916;
+constexpr int ORBIS_NP_WEBAPI_ERROR_MULTIPART_PART_NOT_FOUND = 0x80552917;
+constexpr int ORBIS_NP_WEBAPI_ERROR_PARAMETER_TOO_LONG = 0x80552918;
+constexpr int ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY = 0x80552919;
+constexpr int ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = 0x8055291a;
+constexpr int ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX = 0x8055291b;
+constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055291c;
+constexpr int ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055291d;
+constexpr int ORBIS_NP_WEBAPI_ERROR_AFTER_SEND = 0x8055291e;
+constexpr int ORBIS_NP_WEBAPI_ERROR_TIMEOUT = 0x8055291f;
diff --git a/src/core/libraries/np/np_web_api_internal.cpp b/src/core/libraries/np/np_web_api_internal.cpp
new file mode 100644
index 000000000..3d6c7de86
--- /dev/null
+++ b/src/core/libraries/np/np_web_api_internal.cpp
@@ -0,0 +1,1534 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/config.h"
+#include "common/elf_info.h"
+#include "core/libraries/kernel/process.h"
+#include "core/libraries/kernel/time.h"
+#include "core/libraries/network/http.h"
+#include "np_web_api_internal.h"
+
+#include
+
+namespace Libraries::Np::NpWebApi {
+
+static std::mutex g_global_mutex;
+static std::map g_contexts;
+static s32 g_library_context_count = 0;
+static s32 g_user_context_count = 0;
+static s32 g_handle_count = 0;
+static s32 g_push_event_filter_count = 0;
+static s32 g_service_push_event_filter_count = 0;
+static s32 g_extended_push_event_filter_count = 0;
+static s32 g_registered_callback_count = 0;
+static s64 g_request_count = 0;
+static u64 g_last_timeout_check = 0;
+static s32 g_sdk_ver = 0;
+
+s32 initializeLibrary() {
+ return Kernel::sceKernelGetCompiledSdkVersion(&g_sdk_ver);
+}
+
+s32 getCompiledSdkVersion() {
+ return g_sdk_ver;
+}
+
+s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name, s32 type) {
+ std::scoped_lock lk{g_global_mutex};
+
+ g_library_context_count++;
+ if (g_library_context_count >= 0x8000) {
+ g_library_context_count = 1;
+ }
+ s32 ctx_id = g_library_context_count;
+ while (g_contexts.contains(ctx_id)) {
+ ctx_id--;
+ }
+ if (ctx_id <= 0) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX;
+ }
+
+ // Create new context
+ g_contexts[ctx_id] = new OrbisNpWebApiContext{};
+ auto& new_context = g_contexts.at(ctx_id);
+ new_context->libCtxId = ctx_id;
+ new_context->libHttpCtxId = libHttpCtxId;
+ new_context->type = type;
+ new_context->userCount = 0;
+ new_context->terminated = false;
+ if (name != nullptr) {
+ new_context->name = std::string(name);
+ }
+
+ return ctx_id;
+}
+
+OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag) {
+ std::scoped_lock lk{g_global_mutex};
+ if (libCtxId < 1 || libCtxId >= 0x8000) {
+ return nullptr;
+ }
+ auto& context = g_contexts[libCtxId];
+ std::scoped_lock lk2{context->contextLock};
+ if (flag == 0 && context->terminated) {
+ return nullptr;
+ }
+ context->userCount++;
+ return context;
+}
+
+void releaseContext(OrbisNpWebApiContext* context) {
+ std::scoped_lock lk{context->contextLock};
+ context->userCount--;
+}
+
+bool isContextTerminated(OrbisNpWebApiContext* context) {
+ std::scoped_lock lk{context->contextLock};
+ return context->terminated;
+}
+
+bool isContextBusy(OrbisNpWebApiContext* context) {
+ std::scoped_lock lk{context->contextLock};
+ return context->userCount > 1;
+}
+
+bool areContextHandlesBusy(OrbisNpWebApiContext* context) {
+ std::scoped_lock lk{context->contextLock};
+ bool is_busy = false;
+ for (auto& handle : context->handles) {
+ if (handle.second->userCount > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void lockContext(OrbisNpWebApiContext* context) {
+ context->contextLock.lock();
+}
+
+void unlockContext(OrbisNpWebApiContext* context) {
+ context->contextLock.unlock();
+}
+
+void markContextAsTerminated(OrbisNpWebApiContext* context) {
+ std::scoped_lock lk{context->contextLock};
+ context->terminated = true;
+}
+
+void checkContextTimeout(OrbisNpWebApiContext* context) {
+ u64 time = Kernel::sceKernelGetProcessTime();
+ std::scoped_lock lk{context->contextLock};
+
+ for (auto& user_context : context->userContexts) {
+ checkUserContextTimeout(user_context.second);
+ }
+
+ for (auto& value : context->timerHandles) {
+ auto& timer_handle = value.second;
+ if (!timer_handle->timedOut && timer_handle->handleTimeout != 0 &&
+ timer_handle->handleEndTime < time) {
+ timer_handle->timedOut = true;
+ abortHandle(context->libCtxId, timer_handle->handleId);
+ }
+ }
+}
+
+void checkTimeout() {
+ u64 time = Kernel::sceKernelGetProcessTime();
+ if (time < g_last_timeout_check + 1000) {
+ return;
+ }
+ g_last_timeout_check = time;
+ std::scoped_lock lk{g_global_mutex};
+
+ for (auto& context : g_contexts) {
+ checkContextTimeout(context.second);
+ }
+}
+
+s32 deleteContext(s32 libCtxId) {
+ std::scoped_lock lk{g_global_mutex};
+ if (!g_contexts.contains(libCtxId)) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ auto& context = g_contexts[libCtxId];
+ context->handles.clear();
+ context->timerHandles.clear();
+ context->pushEventFilters.clear();
+ context->servicePushEventFilters.clear();
+ context->extendedPushEventFilters.clear();
+
+ g_contexts.erase(libCtxId);
+ return ORBIS_OK;
+}
+
+s32 terminateContext(s32 libCtxId) {
+ OrbisNpWebApiContext* ctx = findAndValidateContext(libCtxId);
+ if (ctx == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ if (g_sdk_ver < Common::ElfInfo::FW_40 && isContextBusy(ctx)) {
+ releaseContext(ctx);
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_BUSY;
+ }
+
+ std::vector user_context_ids;
+ for (auto& user_context : ctx->userContexts) {
+ user_context_ids.emplace_back(user_context.first);
+ }
+ for (s32 user_context_id : user_context_ids) {
+ s32 result = deleteUserContext(user_context_id);
+ if (result != ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND ||
+ g_sdk_ver < Common::ElfInfo::FW_40) {
+ return result;
+ }
+ }
+
+ lockContext(ctx);
+ if (g_sdk_ver >= Common::ElfInfo::FW_40) {
+ for (auto& handle : ctx->handles) {
+ abortHandle(libCtxId, handle.first);
+ }
+ if (isContextTerminated(ctx)) {
+ unlockContext(ctx);
+ releaseContext(ctx);
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+ markContextAsTerminated(ctx);
+ while (isContextBusy(ctx) || areContextHandlesBusy(ctx)) {
+ unlockContext(ctx);
+ Kernel::sceKernelUsleep(50000);
+ lockContext(ctx);
+ }
+ }
+
+ unlockContext(ctx);
+ releaseContext(ctx);
+ return deleteContext(libCtxId);
+}
+
+OrbisNpWebApiUserContext* findUserContextByUserId(
+ OrbisNpWebApiContext* context, Libraries::UserService::OrbisUserServiceUserId userId) {
+ if (userId == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) {
+ return nullptr;
+ }
+
+ std::scoped_lock lk{context->contextLock};
+ for (auto& user_context : context->userContexts) {
+ if (user_context.second->userId == userId) {
+ user_context.second->userCount++;
+ return user_context.second;
+ }
+ }
+ return nullptr;
+}
+
+OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context, s32 titleUserCtxId) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->userContexts.contains(titleUserCtxId)) {
+ return nullptr;
+ }
+ OrbisNpWebApiUserContext* user_context = context->userContexts[titleUserCtxId];
+ if (user_context->deleted) {
+ return nullptr;
+ }
+ user_context->userCount++;
+ return user_context;
+}
+
+s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId) {
+ LOG_WARNING(Lib_NpWebApi, "called libCtxId = {}", libCtxId);
+
+ Libraries::UserService::OrbisUserServiceUserId user_id = 0;
+ Libraries::UserService::sceUserServiceGetInitialUser(&user_id);
+ return createUserContext(libCtxId, user_id);
+}
+
+s32 createUserContext(s32 libCtxId, Libraries::UserService::OrbisUserServiceUserId userId) {
+ LOG_INFO(Lib_NpWebApi, "libCtxId = {}, userId = {}", libCtxId, userId);
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContextByUserId(context, userId);
+ if (user_context != nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST;
+ }
+
+ std::scoped_lock lk{context->contextLock};
+
+ // Create new user context
+ g_user_context_count++;
+ if (g_user_context_count >= 0x10000) {
+ g_user_context_count = 1;
+ }
+ s32 user_ctx_id = (libCtxId << 0x10) | g_user_context_count;
+ while (context->userContexts.contains(user_ctx_id)) {
+ user_ctx_id--;
+ }
+ if (user_ctx_id <= (libCtxId << 0x10)) {
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_MAX;
+ }
+
+ context->userContexts[user_ctx_id] = new OrbisNpWebApiUserContext{};
+ user_context = context->userContexts.at(user_ctx_id);
+ user_context->userCount = 0;
+ user_context->parentContext = context;
+ user_context->userId = userId;
+ user_context->userCtxId = user_ctx_id;
+ user_context->deleted = false;
+
+ // TODO: Internal structs related to libSceHttp use are initialized here.
+ releaseContext(context);
+ return user_ctx_id;
+}
+
+s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc,
+ void* pUserArg) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ lockContext(context);
+ user_context->notificationCallbackFunction = cbFunc;
+ user_context->pNotificationCallbackUserArgs = pUserArg;
+ unlockContext(context);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 unregisterNotificationCallback(s32 titleUserCtxId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ lockContext(context);
+ user_context->notificationCallbackFunction = nullptr;
+ user_context->pNotificationCallbackUserArgs = nullptr;
+ unlockContext(context);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+bool isUserContextBusy(OrbisNpWebApiUserContext* userContext) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ return userContext->userCount > 1;
+}
+
+bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ bool is_busy = false;
+ for (auto& request : userContext->requests) {
+ request.second->userCount++;
+ bool req_busy = isRequestBusy(request.second);
+ request.second->userCount--;
+ if (req_busy) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void releaseUserContext(OrbisNpWebApiUserContext* userContext) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ userContext->userCount--;
+}
+
+void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ for (auto& request : userContext->requests) {
+ checkRequestTimeout(request.second);
+ }
+}
+
+s32 deleteUserContext(s32 titleUserCtxId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ lockContext(context);
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (g_sdk_ver < Common::ElfInfo::FW_40) {
+ if (isUserContextBusy(user_context)) {
+ releaseUserContext(user_context);
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY;
+ }
+
+ if (areUserContextRequestsBusy(user_context)) {
+ releaseUserContext(user_context);
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_BUSY;
+ }
+ } else {
+ for (auto& request : user_context->requests) {
+ abortRequestInternal(context, user_context, request.second);
+ }
+
+ if (user_context->deleted) {
+ releaseUserContext(user_context);
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ user_context->deleted = true;
+ while (isUserContextBusy(user_context) || areUserContextRequestsBusy(user_context)) {
+ unlockContext(context);
+ Kernel::sceKernelUsleep(50000);
+ lockContext(context);
+ }
+ }
+
+ user_context->extendedPushEventCallbacks.clear();
+ user_context->servicePushEventCallbacks.clear();
+ user_context->pushEventCallbacks.clear();
+ user_context->requests.clear();
+ context->userContexts.erase(titleUserCtxId);
+
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath,
+ OrbisNpWebApiHttpMethod method,
+ const OrbisNpWebApiContentParameter* pContentParameter,
+ const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId,
+ bool isMultipart) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ lockContext(user_context->parentContext);
+ if (g_sdk_ver >= Common::ElfInfo::FW_40 && user_context->deleted) {
+ unlockContext(user_context->parentContext);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ g_request_count++;
+ if (g_request_count >> 0x20 != 0) {
+ g_request_count = 1;
+ }
+
+ s64 user_ctx_id = static_cast(titleUserCtxId);
+ s32 request_id = (user_ctx_id << 0x20) | g_request_count;
+ while (user_context->requests.contains(request_id)) {
+ request_id--;
+ }
+ // Real library would hang if this assert fails.
+ ASSERT_MSG(request_id <= (user_ctx_id << 0x20), "Too many requests!");
+ user_context->requests[request_id] = new OrbisNpWebApiRequest{};
+
+ auto& request = user_context->requests[request_id];
+ request->parentContext = context;
+ request->userCount = 0;
+ request->requestId = request_id;
+ request->userMethod = method;
+ request->multipart = isMultipart;
+ request->aborted = false;
+
+ if (pApiGroup != nullptr) {
+ request->userApiGroup = std::string(pApiGroup);
+ }
+
+ if (pPath != nullptr) {
+ request->userPath = std::string(pPath);
+ }
+
+ if (pContentParameter != nullptr) {
+ request->userContentLength = pContentParameter->contentLength;
+ if (pContentParameter->pContentType != nullptr) {
+ request->userContentType = std::string(pContentParameter->pContentType);
+ }
+ }
+
+ if (pInternalArgs != nullptr) {
+ ASSERT_MSG(pInternalArgs->unk_0 == nullptr && pInternalArgs->unk_1 == nullptr &&
+ pInternalArgs->unk_2 == nullptr,
+ "Internal arguments for requests not supported");
+ }
+
+ unlockContext(user_context->parentContext);
+
+ if (pRequestId != nullptr) {
+ *pRequestId = request->requestId;
+ }
+
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext, s64 requestId) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ if (userContext->requests.contains(requestId)) {
+ return userContext->requests[requestId];
+ }
+
+ return nullptr;
+}
+
+OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext, s64 requestId) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ if (userContext->requests.contains(requestId)) {
+ auto& request = userContext->requests[requestId];
+ request->userCount++;
+ return request;
+ }
+
+ return nullptr;
+}
+
+bool isRequestBusy(OrbisNpWebApiRequest* request) {
+ std::scoped_lock lk{request->parentContext->contextLock};
+ return request->userCount > 1;
+}
+
+s32 setRequestTimeout(s64 requestId, u32 timeout) {
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ request->requestTimeout = timeout;
+
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+void startRequestTimer(OrbisNpWebApiRequest* request) {
+ if (request->requestTimeout != 0 && request->requestEndTime == 0) {
+ request->requestEndTime = Kernel::sceKernelGetProcessTime() + request->requestTimeout;
+ }
+}
+
+void checkRequestTimeout(OrbisNpWebApiRequest* request) {
+ u64 time = Kernel::sceKernelGetProcessTime();
+ if (!request->timedOut && request->requestEndTime != 0 && request->requestEndTime < time) {
+ request->timedOut = true;
+ abortRequest(request->requestId);
+ }
+}
+
+s32 sendRequest(s64 requestId, s32 partIndex, const void* pData, u64 dataSize, s8 flag,
+ const OrbisNpWebApiResponseInformationOption* pRespInfoOption) {
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ startRequestTimer(request);
+
+ // TODO: multipart logic
+
+ if (g_sdk_ver >= Common::ElfInfo::FW_25 && !request->sent) {
+ request->sent = true;
+ }
+
+ lockContext(context);
+ if (!request->timedOut && request->aborted) {
+ unlockContext(context);
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_ABORTED;
+ }
+
+ unlockContext(context);
+
+ // Stubbing sceNpManagerIntGetSigninState call with a config check.
+ if (!Config::getPSNSignedIn()) {
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
+ }
+
+ LOG_ERROR(Lib_NpWebApi,
+ "(STUBBED) called, requestId = {:#x}, pApiGroup = '{}', pPath = '{}', pContentType = "
+ "'{}', method = {}, multipart = {}",
+ requestId, request->userApiGroup, request->userPath, request->userContentType,
+ magic_enum::enum_name(request->userMethod), request->multipart);
+
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext,
+ OrbisNpWebApiRequest* request) {
+ if (context == nullptr || userContext == nullptr || request == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ std::scoped_lock lk{context->contextLock};
+ if (request->aborted) {
+ return ORBIS_OK;
+ }
+
+ request->aborted = true;
+
+ // TODO: Should also abort any Np requests and Http requests tied to this request.
+
+ return ORBIS_OK;
+}
+
+s32 abortRequest(s64 requestId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ s32 result = abortRequestInternal(context, user_context, request);
+
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return result;
+}
+
+void releaseRequest(OrbisNpWebApiRequest* request) {
+ std::scoped_lock lk{request->parentContext->contextLock};
+ request->userCount--;
+}
+
+s32 deleteRequest(s64 requestId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ lockContext(context);
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequestAndMarkBusy(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ if (g_sdk_ver < Common::ElfInfo::FW_40 && isRequestBusy(request)) {
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_BUSY;
+ }
+
+ abortRequestInternal(context, user_context, request);
+ while (isRequestBusy(request)) {
+ unlockContext(context);
+ Kernel::sceKernelUsleep(50000);
+ lockContext(context);
+ }
+
+ releaseRequest(request);
+ user_context->requests.erase(request->requestId);
+
+ releaseUserContext(user_context);
+ unlockContext(context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 createHandleInternal(OrbisNpWebApiContext* context) {
+ g_handle_count++;
+ if (g_handle_count >= 0xf0000000) {
+ g_handle_count = 1;
+ }
+
+ std::scoped_lock lk{context->contextLock};
+
+ s32 handle_id = g_handle_count;
+ context->handles[handle_id] = new OrbisNpWebApiHandle{};
+ auto& handle = context->handles[handle_id];
+ handle->handleId = handle_id;
+ handle->userCount = 0;
+ handle->aborted = false;
+ handle->deleted = false;
+
+ if (g_sdk_ver >= Common::ElfInfo::FW_30) {
+ context->timerHandles[handle_id] = new OrbisNpWebApiTimerHandle{};
+ auto& timer_handle = context->timerHandles[handle_id];
+ timer_handle->handleId = handle_id;
+ timer_handle->timedOut = false;
+ timer_handle->handleTimeout = 0;
+ timer_handle->handleEndTime = 0;
+ }
+
+ return handle_id;
+}
+
+s32 createHandle(s32 libCtxId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = createHandleInternal(context);
+ releaseContext(context);
+ return result;
+}
+
+s32 setHandleTimeoutInternal(OrbisNpWebApiContext* context, s32 handleId, u32 timeout) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->timerHandles.contains(handleId)) {
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+ auto& handle = context->handles[handleId];
+ handle->userCount++;
+
+ auto& timer_handle = context->timerHandles[handleId];
+ timer_handle->handleTimeout = timeout;
+
+ handle->userCount--;
+ return ORBIS_OK;
+}
+
+s32 setHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = setHandleTimeoutInternal(context, handleId, timeout);
+ releaseContext(context);
+ return result;
+}
+
+void startHandleTimer(OrbisNpWebApiContext* context, s32 handleId) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->timerHandles.contains(handleId)) {
+ return;
+ }
+ auto& timer_handle = context->timerHandles[handleId];
+ if (timer_handle->handleTimeout == 0) {
+ return;
+ }
+ timer_handle->handleEndTime = Kernel::sceKernelGetProcessTime() + timer_handle->handleTimeout;
+}
+
+void releaseHandle(OrbisNpWebApiContext* context, OrbisNpWebApiHandle* handle) {
+ if (handle != nullptr) {
+ std::scoped_lock lk{context->contextLock};
+ handle->userCount--;
+ }
+}
+
+s32 getHandle(OrbisNpWebApiContext* context, s32 handleId, OrbisNpWebApiHandle** handleOut) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->handles.contains(handleId)) {
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+ auto& handle = context->handles[handleId];
+ handle->userCount++;
+ if (handleOut != nullptr) {
+ *handleOut = handle;
+ }
+ return ORBIS_OK;
+}
+
+s32 abortHandle(s32 libCtxId, s32 handleId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiHandle* handle;
+ s32 result = getHandle(context, handleId, &handle);
+ if (result == ORBIS_OK) {
+ std::scoped_lock lk{context->contextLock};
+ handle->aborted = true;
+ // TODO: sceNpAsmClientAbortRequest call
+ releaseHandle(context, handle);
+ }
+
+ releaseContext(context);
+ return result;
+}
+
+s32 deleteHandleInternal(OrbisNpWebApiContext* context, s32 handleId) {
+ lockContext(context);
+ if (!context->handles.contains(handleId)) {
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+
+ auto& handle = context->handles[handleId];
+ if (g_sdk_ver >= Common::ElfInfo::FW_40) {
+ if (handle->deleted) {
+ unlockContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+ handle->deleted = true;
+ unlockContext(context);
+ abortHandle(context->libCtxId, handleId);
+ lockContext(context);
+ handle->userCount++;
+ while (handle->userCount > 1) {
+ handle->userCount--;
+ unlockContext(context);
+ Kernel::sceKernelUsleep(50000);
+ lockContext(context);
+ handle->userCount++;
+ }
+ handle->userCount--;
+ } else if (handle->userCount > 0) {
+ unlockContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_BUSY;
+ }
+
+ context->handles.erase(handleId);
+
+ if (g_sdk_ver >= Common::ElfInfo::FW_30 && context->timerHandles.contains(handleId)) {
+ auto& timer_handle = context->timerHandles[handleId];
+ context->timerHandles.erase(handleId);
+ }
+
+ unlockContext(context);
+ return ORBIS_OK;
+}
+
+s32 deleteHandle(s32 libCtxId, s32 handleId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = deleteHandleInternal(context, handleId);
+ releaseContext(context);
+ return result;
+}
+
+s32 createPushEventFilterInternal(OrbisNpWebApiContext* context,
+ const OrbisNpWebApiPushEventFilterParameter* pFilterParam,
+ u64 filterParamNum) {
+ std::scoped_lock lk{context->contextLock};
+ g_push_event_filter_count++;
+ if (g_push_event_filter_count >= 0xf0000000) {
+ g_push_event_filter_count = 1;
+ }
+ s32 filterId = g_push_event_filter_count;
+
+ context->pushEventFilters[filterId] = new OrbisNpWebApiPushEventFilter{};
+ auto& filter = context->pushEventFilters[filterId];
+ filter->parentContext = context;
+ filter->filterId = filterId;
+
+ if (pFilterParam != nullptr && filterParamNum != 0) {
+ for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
+ OrbisNpWebApiPushEventFilterParameter copy = OrbisNpWebApiPushEventFilterParameter{};
+ memcpy(©, &pFilterParam[param_idx], sizeof(OrbisNpWebApiPushEventFilterParameter));
+ filter->filterParams.emplace_back(copy);
+ }
+ }
+ return filterId;
+}
+
+s32 createPushEventFilter(s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam,
+ u64 filterParamNum) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = createPushEventFilterInternal(context, pFilterParam, filterParamNum);
+ releaseContext(context);
+ return result;
+}
+
+s32 deletePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->pushEventFilters.contains(filterId)) {
+ return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ context->pushEventFilters[filterId]->filterParams.clear();
+ context->pushEventFilters.erase(filterId);
+ return ORBIS_OK;
+}
+
+s32 deletePushEventFilter(s32 libCtxId, s32 filterId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = deletePushEventFilterInternal(context, filterId);
+ releaseContext(context);
+ return result;
+}
+
+s32 registerPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId,
+ OrbisNpWebApiPushEventCallback cbFunc, void* pUserArg) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ g_registered_callback_count++;
+ if (g_registered_callback_count >= 0xf0000000) {
+ g_registered_callback_count = 1;
+ }
+ s32 cbId = g_registered_callback_count;
+
+ userContext->pushEventCallbacks[cbId] = new OrbisNpWebApiRegisteredPushEventCallback{};
+ auto& cb = userContext->pushEventCallbacks[cbId];
+ cb->callbackId = cbId;
+ cb->filterId = filterId;
+ cb->cbFunc = cbFunc;
+ cb->pUserArg = pUserArg;
+
+ return cbId;
+}
+
+s32 registerPushEventCallback(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiPushEventCallback cbFunc, void* pUserArg) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (g_sdk_ver >= Common::ElfInfo::FW_25 && !context->pushEventFilters.contains(filterId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ s32 result = registerPushEventCallbackInternal(user_context, filterId, cbFunc, pUserArg);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return result;
+}
+
+s32 unregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (!user_context->pushEventCallbacks.contains(callbackId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
+ }
+
+ lockContext(context);
+ user_context->pushEventCallbacks.erase(callbackId);
+ unlockContext(context);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 createServicePushEventFilterInternal(
+ OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName,
+ OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, u64 filterParamNum) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->handles.contains(handleId)) {
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+ auto& handle = context->handles[handleId];
+ handle->userCount++;
+
+ if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) {
+ // Seems sceNpManagerIntGetUserList fails?
+ LOG_DEBUG(Lib_NpWebApi, "Cannot create service push event while PSN is disabled");
+ handle->userCount--;
+ return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
+ }
+
+ g_service_push_event_filter_count++;
+ if (g_service_push_event_filter_count >= 0xf0000000) {
+ g_service_push_event_filter_count = 1;
+ }
+ s32 filterId = g_service_push_event_filter_count;
+
+ context->servicePushEventFilters[filterId] = new OrbisNpWebApiServicePushEventFilter{};
+ auto& filter = context->servicePushEventFilters[filterId];
+ filter->parentContext = context;
+ filter->filterId = filterId;
+
+ if (pNpServiceName == nullptr) {
+ filter->internal = true;
+ } else {
+ // TODO: if pNpServiceName is non-null, create an np request for this filter.
+ LOG_ERROR(Lib_NpWebApi, "Np behavior not handled");
+ filter->npServiceName = std::string(pNpServiceName);
+ }
+
+ filter->npServiceLabel = npServiceLabel;
+
+ if (pFilterParam != nullptr && filterParamNum != 0) {
+ for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
+ OrbisNpWebApiServicePushEventFilterParameter copy =
+ OrbisNpWebApiServicePushEventFilterParameter{};
+ memcpy(©, &pFilterParam[param_idx],
+ sizeof(OrbisNpWebApiServicePushEventFilterParameter));
+ filter->filterParams.emplace_back(copy);
+ }
+ }
+
+ handle->userCount--;
+ return filterId;
+}
+
+s32 createServicePushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName,
+ OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam,
+ u64 filterParamNum) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ startHandleTimer(context, handleId);
+ s32 result = createServicePushEventFilterInternal(context, handleId, pNpServiceName,
+ npServiceLabel, pFilterParam, filterParamNum);
+ releaseContext(context);
+ return result;
+}
+
+s32 deleteServicePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->servicePushEventFilters.contains(filterId)) {
+ return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ context->servicePushEventFilters[filterId]->filterParams.clear();
+ context->servicePushEventFilters.erase(filterId);
+ return ORBIS_OK;
+}
+
+s32 deleteServicePushEventFilter(s32 libCtxId, s32 filterId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = deleteServicePushEventFilterInternal(context, filterId);
+ releaseContext(context);
+ return result;
+}
+
+s32 registerServicePushEventCallbackInternal(
+ OrbisNpWebApiUserContext* userContext, s32 filterId,
+ OrbisNpWebApiServicePushEventCallback cbFunc,
+ OrbisNpWebApiInternalServicePushEventCallback intCbFunc,
+ OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, void* pUserArg) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+ if (cbFunc == nullptr && intCbFunc == nullptr && intCbFuncA == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+
+ g_registered_callback_count++;
+ if (g_registered_callback_count >= 0xf0000000) {
+ g_registered_callback_count = 1;
+ }
+ s32 cbId = g_registered_callback_count;
+
+ userContext->servicePushEventCallbacks[cbId] =
+ new OrbisNpWebApiRegisteredServicePushEventCallback{};
+ auto& cb = userContext->servicePushEventCallbacks[cbId];
+ cb->callbackId = cbId;
+ cb->filterId = filterId;
+ cb->cbFunc = cbFunc;
+ cb->internalCbFunc = intCbFunc;
+ cb->internalCbFuncA = intCbFuncA;
+ cb->pUserArg = pUserArg;
+
+ return cbId;
+}
+
+s32 registerServicePushEventCallback(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiServicePushEventCallback cbFunc,
+ OrbisNpWebApiInternalServicePushEventCallback intCbFunc,
+ OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA,
+ void* pUserArg) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (g_sdk_ver >= Common::ElfInfo::FW_25 &&
+ !context->servicePushEventFilters.contains(filterId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ s32 result = registerServicePushEventCallbackInternal(user_context, filterId, cbFunc, intCbFunc,
+ intCbFuncA, pUserArg);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return result;
+}
+
+s32 unregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (!user_context->servicePushEventCallbacks.contains(callbackId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND;
+ }
+
+ lockContext(context);
+ user_context->servicePushEventCallbacks.erase(callbackId);
+ unlockContext(context);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 createExtendedPushEventFilterInternal(
+ OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName,
+ OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum,
+ bool internal) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->handles.contains(handleId)) {
+ return ORBIS_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND;
+ }
+ auto& handle = context->handles[handleId];
+ handle->userCount++;
+
+ if (pNpServiceName != nullptr && !Config::getPSNSignedIn()) {
+ // Seems sceNpManagerIntGetUserList fails?
+ LOG_DEBUG(Lib_NpWebApi, "Cannot create extended push event while PSN is disabled");
+ handle->userCount--;
+ return ORBIS_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
+ }
+
+ g_extended_push_event_filter_count++;
+ if (g_extended_push_event_filter_count >= 0xf0000000) {
+ g_extended_push_event_filter_count = 1;
+ }
+ s32 filterId = g_extended_push_event_filter_count;
+
+ context->extendedPushEventFilters[filterId] = new OrbisNpWebApiExtendedPushEventFilter{};
+ auto& filter = context->extendedPushEventFilters[filterId];
+ filter->internal = internal;
+ filter->parentContext = context;
+ filter->filterId = filterId;
+
+ if (pNpServiceName == nullptr) {
+ npServiceLabel = ORBIS_NP_INVALID_SERVICE_LABEL;
+ } else {
+ // TODO: if pNpServiceName is non-null, create an np request for this filter.
+ LOG_ERROR(Lib_NpWebApi, "Np behavior not handled");
+ filter->npServiceName = std::string(pNpServiceName);
+ }
+
+ filter->npServiceLabel = npServiceLabel;
+
+ if (pFilterParam != nullptr && filterParamNum != 0) {
+ for (u64 param_idx = 0; param_idx < filterParamNum; param_idx++) {
+ OrbisNpWebApiExtdPushEventFilterParameter copy =
+ OrbisNpWebApiExtdPushEventFilterParameter{};
+ memcpy(©, &pFilterParam[param_idx],
+ sizeof(OrbisNpWebApiExtdPushEventFilterParameter));
+ filter->filterParams.emplace_back(copy);
+ // TODO: Every parameter is registered with an extended data filter through
+ // sceNpPushRegisterExtendedDataFilter
+ }
+ }
+
+ handle->userCount--;
+ return filterId;
+}
+
+s32 createExtendedPushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName,
+ OrbisNpServiceLabel npServiceLabel,
+ const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam,
+ u64 filterParamNum, bool internal) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ startHandleTimer(context, handleId);
+ s32 result = createExtendedPushEventFilterInternal(
+ context, handleId, pNpServiceName, npServiceLabel, pFilterParam, filterParamNum, internal);
+ releaseContext(context);
+ return result;
+}
+
+s32 deleteExtendedPushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId) {
+ std::scoped_lock lk{context->contextLock};
+ if (!context->extendedPushEventFilters.contains(filterId)) {
+ return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ context->extendedPushEventFilters[filterId]->filterParams.clear();
+ context->extendedPushEventFilters.erase(filterId);
+ return ORBIS_OK;
+}
+
+s32 deleteExtendedPushEventFilter(s32 libCtxId, s32 filterId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(libCtxId);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ s32 result = deleteExtendedPushEventFilterInternal(context, filterId);
+ releaseContext(context);
+ return result;
+}
+
+s32 registerExtdPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId,
+ OrbisNpWebApiExtdPushEventCallback cbFunc,
+ OrbisNpWebApiExtdPushEventCallbackA cbFuncA,
+ void* pUserArg) {
+ std::scoped_lock lk{userContext->parentContext->contextLock};
+
+ g_registered_callback_count++;
+ if (g_registered_callback_count >= 0xf0000000) {
+ g_registered_callback_count = 1;
+ }
+
+ if (cbFunc == nullptr && cbFuncA == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
+ }
+ s32 cbId = g_registered_callback_count;
+
+ userContext->extendedPushEventCallbacks[cbId] =
+ new OrbisNpWebApiRegisteredExtendedPushEventCallback{};
+ auto& cb = userContext->extendedPushEventCallbacks[cbId];
+ cb->callbackId = cbId;
+ cb->filterId = filterId;
+ cb->cbFunc = cbFunc;
+ cb->cbFuncA = cbFuncA;
+ cb->pUserArg = pUserArg;
+
+ return cbId;
+}
+
+s32 registerExtdPushEventCallback(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiExtdPushEventCallback cbFunc,
+ OrbisNpWebApiExtdPushEventCallbackA cbFuncA, void* pUserArg) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (!context->extendedPushEventFilters.contains(filterId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_FILTER_NOT_FOUND;
+ }
+
+ s32 result =
+ registerExtdPushEventCallbackInternal(user_context, filterId, cbFunc, cbFuncA, pUserArg);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return result;
+}
+
+s32 registerExtdPushEventCallbackA(s32 titleUserCtxId, s32 filterId,
+ OrbisNpWebApiExtdPushEventCallbackA cbFunc, void* pUserArg) {
+ return registerExtdPushEventCallback(titleUserCtxId, filterId, nullptr, cbFunc, pUserArg);
+}
+
+s32 unregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId) {
+ OrbisNpWebApiContext* context = findAndValidateContext(titleUserCtxId >> 0x10);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, titleUserCtxId);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ if (!user_context->extendedPushEventCallbacks.contains(callbackId)) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_EXTD_PUSH_EVENT_CALLBACK_NOT_FOUND;
+ }
+
+ lockContext(context);
+ user_context->extendedPushEventCallbacks.erase(callbackId);
+ unlockContext(context);
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_OK;
+}
+
+s32 PS4_SYSV_ABI getHttpRequestIdFromRequest(OrbisNpWebApiRequest* request)
+
+{
+ return request->requestId;
+}
+
+s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code) {
+ s32 status_code;
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ // Query HTTP layer
+ {
+ int32_t httpReqId = getHttpRequestIdFromRequest(request);
+ s32 err = Libraries::Http::sceHttpGetStatusCode(httpReqId, &status_code);
+
+ if (out_status_code != nullptr)
+ *out_status_code = status_code;
+
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+
+ return err;
+ }
+}
+
+void PS4_SYSV_ABI setRequestEndTime(OrbisNpWebApiRequest* request) {
+ u64 time;
+ if ((request->requestTimeout != 0) && (request->requestEndTime == 0)) {
+ time = Libraries::Kernel::sceKernelGetProcessTime();
+ request->requestEndTime = (u64)request->requestTimeout + time;
+ }
+}
+
+void PS4_SYSV_ABI clearRequestEndTime(OrbisNpWebApiRequest* req) {
+ req->requestEndTime = 0;
+ return;
+}
+
+bool PS4_SYSV_ABI hasRequestTimedOut(OrbisNpWebApiRequest* request) {
+ return request->timedOut;
+}
+
+bool PS4_SYSV_ABI isRequestAborted(OrbisNpWebApiRequest* request) {
+ return request->aborted;
+}
+
+void PS4_SYSV_ABI setRequestState(OrbisNpWebApiRequest* request, u8 state) {
+ request->requestState = state;
+}
+
+u64 PS4_SYSV_ABI copyRequestData(OrbisNpWebApiRequest* request, void* data, u64 size) {
+ u64 readSize = 0;
+
+ if (request->remainingData != 0) {
+ u64 remainingSize = request->remainingData - request->readOffset;
+
+ if (remainingSize != 0) {
+ if (remainingSize < size) {
+ size = remainingSize;
+ }
+ memcpy(data, request->data + request->readOffset, size);
+ request->readOffset += static_cast(size);
+ readSize = size;
+ }
+ }
+ return readSize;
+}
+
+s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size) {
+ u32 offset;
+ s32 result;
+ u64 remainingSize;
+ u64 bytesCopied;
+
+ OrbisNpWebApiContext* context = findAndValidateContext(requestId >> 0x30);
+ if (context == nullptr) {
+ return ORBIS_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiUserContext* user_context = findUserContext(context, requestId >> 0x20);
+ if (user_context == nullptr) {
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND;
+ }
+
+ OrbisNpWebApiRequest* request = findRequest(user_context, requestId);
+ if (request == nullptr) {
+ releaseUserContext(user_context);
+ releaseContext(context);
+ return ORBIS_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND;
+ }
+
+ setRequestEndTime(request);
+
+ bytesCopied = copyRequestData(request, pData, size);
+ offset = (u32)bytesCopied;
+ remainingSize = size - offset;
+
+ // If caller wants more data than buffered
+ if (remainingSize != 0) {
+ lockContext(context);
+ setRequestState(request, 5); // TODO add request states?
+
+ if (!hasRequestTimedOut(request) && isRequestAborted(request)) {
+ unlockContext(context);
+ offset = ORBIS_NP_WEBAPI_ERROR_ABORTED;
+ } else {
+ unlockContext(context);
+
+ int32_t httpReqId = getHttpRequestIdFromRequest(request);
+ int32_t httpRead =
+ Libraries::Http::sceHttpReadData(httpReqId, (u8*)pData + offset, remainingSize);
+
+ if (httpRead < 0)
+ httpRead = 0;
+
+ offset += httpRead;
+ }
+ }
+
+ // Final state resolution
+ lockContext(context);
+ setRequestState(request, 0);
+
+ if (hasRequestTimedOut(request)) {
+ result = ORBIS_NP_WEBAPI_ERROR_TIMEOUT;
+ } else if (isRequestAborted(request)) {
+ result = ORBIS_NP_WEBAPI_ERROR_ABORTED;
+ } else {
+ result = offset;
+ }
+
+ unlockContext(context);
+
+ // Cleanup
+ clearRequestEndTime(request);
+ releaseRequest(request);
+ releaseUserContext(user_context);
+ releaseContext(context);
+
+ return result;
+}
+
+}; // namespace Libraries::Np::NpWebApi
\ No newline at end of file
diff --git a/src/core/libraries/np/np_web_api_internal.h b/src/core/libraries/np/np_web_api_internal.h
new file mode 100644
index 000000000..571df0ab9
--- /dev/null
+++ b/src/core/libraries/np/np_web_api_internal.h
@@ -0,0 +1,301 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+#include