diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f388c970..cb29184fe 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 ) @@ -598,6 +600,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..657943c95 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -14,6 +15,8 @@ #include "common/path_util.h" #include "common/scm_rev.h" +#include "input/input_handler.h" + using std::nullopt; using std::optional; using std::string; @@ -361,7 +364,7 @@ u32 getWindowHeight() { } u32 getInternalScreenWidth() { - return internalScreenHeight.get(); + return internalScreenWidth.get(); } u32 getInternalScreenHeight() { @@ -1289,16 +1292,6 @@ void setDefaultValues(bool is_game_specific) { constexpr std::string_view GetDefaultGlobalConfig() { return R"(# Anything put here will be loaded for all games, # alongside the game's config or default.ini depending on your preference. - -hotkey_renderdoc_capture = f12 -hotkey_fullscreen = f11 -hotkey_show_fps = f10 -hotkey_pause = f9 -hotkey_reload_inputs = f8 -hotkey_toggle_mouse_to_joystick = f7 -hotkey_toggle_mouse_to_gyro = f6 -hotkey_toggle_mouse_to_touchpad = delete -hotkey_quit = lctrl, lshift, end )"; } @@ -1376,7 +1369,7 @@ analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } -std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { +std::filesystem::path GetInputConfigFile(const string& game_id) { // Read configuration file of the game, and if it doesn't exist, generate it from default // If that doesn't exist either, generate that from getDefaultConfig() and try again // If even the folder is missing, we start with that. @@ -1415,6 +1408,39 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { } } } + if (game_id == "global") { + std::map default_bindings_to_add = { + {"hotkey_renderdoc_capture", "f12"}, + {"hotkey_fullscreen", "f11"}, + {"hotkey_show_fps", "f10"}, + {"hotkey_pause", "f9"}, + {"hotkey_reload_inputs", "f8"}, + {"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"}, + }; + std::ifstream global_in(config_file); + string line; + while (std::getline(global_in, line)) { + line.erase(std::remove_if(line.begin(), line.end(), + [](unsigned char c) { return std::isspace(c); }), + line.end()); + std::size_t equal_pos = line.find('='); + if (equal_pos == std::string::npos) { + continue; + } + std::string output_string = line.substr(0, equal_pos); + default_bindings_to_add.erase(output_string); + } + global_in.close(); + std::ofstream global_out(config_file, std::ios::app); + for (auto const& b : default_bindings_to_add) { + global_out << b.first << " = " << b.second << "\n"; + } + } // If game-specific config doesn't exist, create it from the default config if (!std::filesystem::exists(config_file)) { diff --git a/src/common/config.h b/src/common/config.h index 2a95e6cf0..036d04c99 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -183,6 +183,6 @@ std::filesystem::path getAddonInstallDir(); void setDefaultValues(bool is_game_specific = false); constexpr std::string_view GetDefaultGlobalConfig(); -std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); +std::filesystem::path GetInputConfigFile(const std::string& game_id = ""); }; // namespace Config 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/logging/backend.cpp b/src/common/logging/backend.cpp index d7c816da3..168350b96 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -208,26 +209,41 @@ public: } } + std::unique_lock entry_loc(_mutex); + + if (_last_entry.message == message) { + ++_last_entry.counter; + return; + } + + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; - const Entry entry = { + this->_last_entry = { .timestamp = duration_cast(steady_clock::now() - time_origin), .log_class = log_class, .log_level = log_level, .filename = filename, .line_num = line_num, .function = function, - .message = std::move(message), + .message = message, .thread = Common::GetCurrentThreadName(), + .counter = 1, }; - if (Config::getLogType() == "async") { - message_queue.EmplaceWait(entry); - } else { - ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); - std::fflush(stdout); - } } private: @@ -259,6 +275,22 @@ private: } void StopBackendThread() { + // log last message + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + + this->_last_entry = {}; + backend_thread.request_stop(); if (backend_thread.joinable()) { backend_thread.join(); @@ -292,6 +324,8 @@ private: MPSCQueue message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; std::jthread backend_thread; + Entry _last_entry; + std::mutex _mutex; }; } // namespace diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h index 6c529f878..7b52ad7e1 100644 --- a/src/common/logging/log_entry.h +++ b/src/common/logging/log_entry.h @@ -22,6 +22,7 @@ struct Entry { std::string function; std::string message; std::string thread; + u32 counter = 0; }; } // namespace Common::Log diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 928040fec..4be107713 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -32,6 +32,9 @@ static bool show_simple_fps = false; static bool visibility_toggled = false; static bool show_quit_window = false; +static bool show_volume = false; +static float volume_start_time; + static float fps_scale = 1.0f; static int dump_frame_count = 1; @@ -454,6 +457,27 @@ void L::Draw() { End(); } + if (show_volume) { + float current_time = ImGui::GetTime(); + + // Show volume for 3 seconds + if (current_time - volume_start_time >= 3.0) { + show_volume = false; + } else { + SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->WorkPos.x + + ImGui::GetMainViewport()->WorkSize.x - 10, + ImGui::GetMainViewport()->WorkPos.y + 10), + ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + + if (ImGui::Begin("Volume Window", &show_volume, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) { + Text("Volume: %d", Config::getVolumeSlider()); + } + End(); + } + } + PopID(); } @@ -482,4 +506,9 @@ void ToggleQuitWindow() { show_quit_window = !show_quit_window; } +void ShowVolume() { + volume_start_time = ImGui::GetTime(); + show_volume = true; +} + } // namespace Overlay diff --git a/src/core/devtools/layer.h b/src/core/devtools/layer.h index 96b48a7f0..761135baf 100644 --- a/src/core/devtools/layer.h +++ b/src/core/devtools/layer.h @@ -32,5 +32,6 @@ namespace Overlay { void ToggleSimpleFps(); void SetSimpleFps(bool enabled); void ToggleQuitWindow(); +void ShowVolume(); } // namespace Overlay diff --git a/src/core/emulator_state.cpp b/src/core/emulator_state.cpp index 1f02043a3..20cffe53c 100644 --- a/src/core/emulator_state.cpp +++ b/src/core/emulator_state.cpp @@ -35,3 +35,11 @@ bool EmulatorState::IsAutoPatchesLoadEnabled() const { void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) { m_load_patches_auto = enable; } + +bool EmulatorState::IsGameSpecifigConfigUsed() const { + return m_game_specific_config_used; +} + +void EmulatorState::SetGameSpecifigConfigUsed(bool used) { + m_game_specific_config_used = used; +} diff --git a/src/core/emulator_state.h b/src/core/emulator_state.h index c12af5401..0764b8a81 100644 --- a/src/core/emulator_state.h +++ b/src/core/emulator_state.h @@ -18,6 +18,8 @@ public: void SetGameRunning(bool running); bool IsAutoPatchesLoadEnabled() const; void SetAutoPatchesLoadEnabled(bool enable); + bool IsGameSpecifigConfigUsed() const; + void SetGameSpecifigConfigUsed(bool used); private: static std::shared_ptr s_instance; @@ -26,4 +28,5 @@ private: // state variables bool m_running = false; bool m_load_patches_auto = true; -}; \ No newline at end of file + bool m_game_specific_config_used = false; +}; 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..100ddd51c 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,816 @@ 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_DEBUG(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_INFO(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; +} + +s32 PS4_SYSV_ABI sceAudioOutGetSystemState(OrbisAudioOutSystemState* 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; + } + if (state == nullptr) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER; + } + memset(state, 0, sizeof(*state)); + LOG_DEBUG(Lib_AudioOut, "called"); + 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 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..ffc1f9eb1 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 { @@ -70,6 +97,12 @@ struct OrbisAudioOutPortState { u64 reserved64[2]; }; +struct OrbisAudioOutSystemState { + float loudness; + u8 reserved8[4]; + u64 reserved64[3]; +}; + struct AudioFormatInfo { bool is_float; u8 sample_size; @@ -77,6 +110,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 +121,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(OrbisAudioOutSystemState* state); +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..ce2598759 --- /dev/null +++ b/src/core/libraries/audio/sdl_audio_out.cpp @@ -0,0 +1,600 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#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" + +// SIMD support detection +#if defined(__x86_64__) || defined(_M_X64) +#include +#define HAS_SSE2 +#endif + +#define SDL_INVALID_AUDIODEVICEID 0 + +namespace Libraries::AudioOut { + +// Volume constants +constexpr float VOLUME_0DB = 32768.0f; // 1 << 15 +constexpr float INV_VOLUME_0DB = 1.0f / VOLUME_0DB; +constexpr float VOLUME_EPSILON = 0.001f; +// Timing constants +constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms +constexpr u64 MIN_SLEEP_THRESHOLD_US = 10; +constexpr u64 TIMING_RESYNC_THRESHOLD_US = 100000; // Resync if >100ms behind + +// Queue management +constexpr u32 QUEUE_MULTIPLIER = 4; +// Memory alignment for SIMD +constexpr size_t AUDIO_BUFFER_ALIGNMENT = 32; + +// Channel positions +enum ChannelPos : u8 { + 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) { + + if (!Initialize(port.type)) { + LOG_ERROR(Lib_AudioOut, "Failed to initialize SDL audio backend"); + } + } + + ~SDLPortBackend() override { + Cleanup(); + } + + void Output(void* ptr) override { + if (!stream || !internal_buffer || !convert) [[unlikely]] { + return; + } + + if (ptr == nullptr) [[unlikely]] { + return; + } + + UpdateVolumeIfChanged(); + const u64 current_time = Kernel::sceKernelGetProcessTime(); + convert(ptr, internal_buffer, buffer_frames, nullptr); + HandleTiming(current_time); + + if ((output_count++ & 0xF) == 0) { // Check every 16 outputs + ManageAudioQueue(); + } + + if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) [[unlikely]] { + LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError()); + } + + last_output_time.store(current_time, std::memory_order_release); + } + + void SetVolume(const std::array& ch_volumes) override { + if (!stream) [[unlikely]] { + return; + } + + float max_channel_gain = 0.0f; + const u32 channels_to_check = std::min(num_channels, 8u); + + for (u32 i = 0; i < channels_to_check; i++) { + const float channel_gain = static_cast(ch_volumes[i]) * INV_VOLUME_0DB; + max_channel_gain = std::max(max_channel_gain, channel_gain); + } + + const float slider_gain = Config::getVolumeSlider() * 0.01f; // Faster than /100.0f + const float total_gain = max_channel_gain * slider_gain; + + const float current = current_gain.load(std::memory_order_acquire); + if (std::abs(total_gain - current) < VOLUME_EPSILON) { + return; + } + + // Apply volume change + if (SDL_SetAudioStreamGain(stream, total_gain)) { + current_gain.store(total_gain, std::memory_order_release); + LOG_DEBUG(Lib_AudioOut, + "Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})", + total_gain, max_channel_gain, slider_gain); + } else { + LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); + } + } + + u64 GetLastOutputTime() const { + return last_output_time.load(std::memory_order_acquire); + } + +private: + bool Initialize(OrbisAudioOutPort type) { + // Calculate timing parameters + period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate; + + // Allocate aligned internal buffer for SIMD operations + internal_buffer_size = buffer_frames * sizeof(float) * num_channels; + +#ifdef _WIN32 + internal_buffer = _aligned_malloc(internal_buffer_size, AUDIO_BUFFER_ALIGNMENT); +#else + if (posix_memalign(&internal_buffer, AUDIO_BUFFER_ALIGNMENT, internal_buffer_size) != 0) { + internal_buffer = nullptr; + } +#endif + + if (!internal_buffer) { + LOG_ERROR(Lib_AudioOut, "Failed to allocate aligned audio buffer of size {}", + internal_buffer_size); + return false; + } + + // Initialize current gain + current_gain.store(Config::getVolumeSlider() * 0.01f, std::memory_order_relaxed); + + if (!SelectConverter()) { + FreeAlignedBuffer(); + return false; + } + + // Open SDL device + if (!OpenDevice(type)) { + FreeAlignedBuffer(); + return false; + } + + CalculateQueueThreshold(); + return true; + } + + void Cleanup() { + if (stream) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + } + FreeAlignedBuffer(); + } + + void FreeAlignedBuffer() { + if (internal_buffer) { +#ifdef _WIN32 + _aligned_free(internal_buffer); +#else + free(internal_buffer); +#endif + internal_buffer = nullptr; + } + } + + void UpdateVolumeIfChanged() { + const u64 current_time = Kernel::sceKernelGetProcessTime(); + + if (current_time - last_volume_check_time < VOLUME_CHECK_INTERVAL_US) { + return; + } + + last_volume_check_time = current_time; + + const float config_volume = Config::getVolumeSlider() * 0.01f; + const float stored_gain = current_gain.load(std::memory_order_acquire); + + // Only update if the difference is significant + if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) { + if (SDL_SetAudioStreamGain(stream, config_volume)) { + current_gain.store(config_volume, std::memory_order_release); + LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume); + } else { + LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError()); + } + } + } + + void HandleTiming(u64 current_time) { + if (next_output_time == 0) [[unlikely]] { + // First output - set initial timing + next_output_time = current_time + period_us; + return; + } + + const s64 time_diff = static_cast(current_time - next_output_time); + + if (time_diff > static_cast(TIMING_RESYNC_THRESHOLD_US)) [[unlikely]] { + // We're far behind - resync + next_output_time = current_time + period_us; + } else if (time_diff < 0) { + // We're ahead of schedule - wait + const u64 time_to_wait = static_cast(-time_diff); + next_output_time += period_us; + + if (time_to_wait > MIN_SLEEP_THRESHOLD_US) { + // Sleep for most of the wait period + const u64 sleep_duration = time_to_wait - MIN_SLEEP_THRESHOLD_US; + std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration)); + } + } else { + // Slightly behind or on time - just advance + next_output_time += period_us; + } + } + + void ManageAudioQueue() { + const auto queued = SDL_GetAudioStreamQueued(stream); + + if (queued >= queue_threshold) [[unlikely]] { + LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued, + queue_threshold); + SDL_ClearAudioStream(stream); + CalculateQueueThreshold(); + } + } + + bool OpenDevice(OrbisAudioOutPort type) { + const SDL_AudioSpec fmt = { + .format = SDL_AUDIO_F32LE, + .channels = static_cast(num_channels), + .freq = static_cast(sample_rate), + }; + + // Determine device + const std::string device_name = GetDeviceName(type); + const SDL_AudioDeviceID dev_id = SelectAudioDevice(device_name, type); + + if (dev_id == SDL_INVALID_AUDIODEVICEID) { + return false; + } + + // 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; + } + + // Configure channel mapping + if (!ConfigureChannelMap()) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + return false; + } + + // Set initial volume + const float initial_gain = current_gain.load(std::memory_order_relaxed); + 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; + } + + SDL_AudioDeviceID SelectAudioDevice(const std::string& device_name, OrbisAudioOutPort type) { + if (device_name == "None") { + LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}", + static_cast(type)); + return SDL_INVALID_AUDIODEVICEID; + } + + if (device_name.empty() || device_name == "Default Device") { + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + // Search for specific device + int num_devices = 0; + SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices); + + if (!dev_array) { + LOG_WARNING(Lib_AudioOut, "No audio devices found, using default"); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + SDL_AudioDeviceID selected_device = SDL_INVALID_AUDIODEVICEID; + + for (int i = 0; i < num_devices; i++) { + const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]); + if (dev_name && device_name == dev_name) { + selected_device = dev_array[i]; + break; + } + } + + SDL_free(dev_array); + + if (selected_device == SDL_INVALID_AUDIODEVICEID) { + LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default", device_name); + return SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + return selected_device; + } + + bool ConfigureChannelMap() { + if (num_channels == 0) { + return true; + } + + std::vector channel_map(num_channels); + + if (is_std && num_channels == 8) { + // Standard 8CH layout requires remapping + channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR}; + } else { + std::copy_n(channel_layout.begin(), num_channels, channel_map.begin()); + } + + if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) { + LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError()); + return false; + } + + return true; + } + + std::string GetDeviceName(OrbisAudioOutPort type) const { + switch (type) { + case OrbisAudioOutPort::Main: + case OrbisAudioOutPort::Bgm: + return Config::getMainOutputDevice(); + case OrbisAudioOutPort::PadSpk: + return Config::getPadSpkOutputDevice(); + default: + return Config::getMainOutputDevice(); + } + } + + bool SelectConverter() { + if (is_float) { + switch (num_channels) { + case 1: + convert = &ConvertF32Mono; + break; + case 2: + convert = &ConvertF32Stereo; + break; + case 8: + convert = is_std ? &ConvertF32Std8CH : &ConvertF32_8CH; + break; + default: + LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels); + return false; + } + } else { + switch (num_channels) { + case 1: + convert = &ConvertS16Mono; + break; + case 2: +#if defined(HAS_SSE2) + convert = &ConvertS16StereoSIMD; +#else + convert = &ConvertS16Stereo; +#endif + break; + case 8: +#if defined(HAS_SSE2) + convert = &ConvertS16_8CH_SIMD; +#else + convert = &ConvertS16_8CH; +#endif + break; + default: + LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels); + return false; + } + } + + return true; + } + + void CalculateQueueThreshold() { + if (!stream) { + return; + } + + SDL_AudioSpec discard; + int sdl_buffer_frames = 0; + + if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard, + &sdl_buffer_frames)) { + LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError()); + } + + const u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels; + queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * QUEUE_MULTIPLIER; + + 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); + + static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + for (u32 i = 0; i < frames; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + + static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const u32 num_samples = frames << 1; // * 2 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + +#ifdef HAS_SSE2 + static void ConvertS16StereoSIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 1; + u32 i = 0; + + // Process 8 samples at a time (4 stereo frames) + for (; i + 8 <= num_samples; i += 8) { + // Load 8 s16 values + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + + // Convert to 32-bit integers + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + + // Convert to float and scale + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + + // Store results + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + // Handle remaining samples + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + + static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const u32 num_samples = frames << 3; // * 8 + for (u32 i = 0; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } + +#ifdef HAS_SSE2 + static void ConvertS16_8CH_SIMD(const void* src, void* dst, u32 frames, const float*) { + const s16* s = static_cast(src); + float* d = static_cast(dst); + + const __m128 scale = _mm_set1_ps(INV_VOLUME_0DB); + const u32 num_samples = frames << 3; + u32 i = 0; + + // Process 8 samples at a time + for (; i + 8 <= num_samples; i += 8) { + __m128i s16_vals = _mm_loadu_si128(reinterpret_cast(&s[i])); + __m128i s32_lo = _mm_cvtepi16_epi32(s16_vals); + __m128i s32_hi = _mm_cvtepi16_epi32(_mm_srli_si128(s16_vals, 8)); + __m128 f_lo = _mm_mul_ps(_mm_cvtepi32_ps(s32_lo), scale); + __m128 f_hi = _mm_mul_ps(_mm_cvtepi32_ps(s32_hi), scale); + _mm_storeu_ps(&d[i], f_lo); + _mm_storeu_ps(&d[i + 4], f_hi); + } + + for (; i < num_samples; i++) { + d[i] = s[i] * INV_VOLUME_0DB; + } + } +#endif + + 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); + + // Channel remapping for standard 8CH layout + for (u32 i = 0; i < frames; i++) { + const u32 offset = i << 3; // * 8 + + d[offset + FL] = s[offset + FL]; + d[offset + FR] = s[offset + FR]; + d[offset + FC] = s[offset + FC]; + d[offset + LF] = s[offset + LF]; + d[offset + SL] = s[offset + STD_SL]; + d[offset + SR] = s[offset + STD_SR]; + d[offset + BL] = s[offset + STD_BL]; + d[offset + BR] = s[offset + STD_BR]; + } + } + + // Audio format parameters + const u32 frame_size; + const u32 guest_buffer_size; + const u32 buffer_frames; + const u32 sample_rate; + const u32 num_channels; + const bool is_float; + const bool is_std; + const std::array channel_layout; + + alignas(64) u64 period_us{0}; + alignas(64) std::atomic last_output_time{0}; + u64 next_output_time{0}; + u64 last_volume_check_time{0}; + u32 output_count{0}; + + // Buffers + u32 internal_buffer_size{0}; + void* internal_buffer{nullptr}; + + // Converter function pointer + ConverterFunc convert{nullptr}; + + // Volume management + alignas(64) std::atomic current_gain{1.0f}; + + // SDL audio stream + SDL_AudioStream* stream{nullptr}; + u32 queue_threshold{0}; +}; + +std::unique_ptr SDLAudioOut::Open(PortOut& port) { + return std::make_unique(port); +} + +} // namespace Libraries::AudioOut 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/process.cpp b/src/core/libraries/kernel/process.cpp index 4ea7fa062..31cc9a81b 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -191,6 +191,26 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfo(s32 handle, Core::OrbisKernelModuleInfo* return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleInfo2(s32 handle, Core::OrbisKernelModuleInfo* info) { + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (info->st_size != sizeof(Core::OrbisKernelModuleInfo)) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + if (module == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + if (module->IsSystemLib()) { + return ORBIS_KERNEL_ERROR_EPERM; + } + *info = module->GetModuleInfo(); + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceKernelGetModuleInfoInternal(s32 handle, Core::OrbisKernelModuleInfoEx* info) { if (info == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; @@ -230,6 +250,31 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList(s32* handles, u64 num_array, u64* out_co return ORBIS_OK; } +s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_count) { + if (handles == nullptr || out_count == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + + auto* linker = Common::Singleton::Instance(); + u64 id = 0; + u64 index = 0; + auto* module = linker->GetModule(id); + while (module != nullptr && index < num_array) { + if (!module->IsSystemLib()) { + handles[index++] = id; + } + id++; + module = linker->GetModule(id); + } + + if (index == num_array && module != nullptr) { + return ORBIS_KERNEL_ERROR_ENOMEM; + } + + *out_count = index; + return ORBIS_OK; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -249,8 +294,11 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("RpQJJVKTiFM", "libkernel", 1, "libkernel", sceKernelGetModuleInfoForUnwind); LIB_FUNCTION("f7KBOafysXo", "libkernel", 1, "libkernel", sceKernelGetModuleInfoFromAddr); LIB_FUNCTION("kUpgrXIrz7Q", "libkernel", 1, "libkernel", sceKernelGetModuleInfo); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel", 1, "libkernel", sceKernelGetModuleInfo2); + LIB_FUNCTION("QgsKEUfkqMA", "libkernel_module_info", 1, "libkernel", sceKernelGetModuleInfo2); LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal); LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList); + LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit); } diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 7a046e973..31e8b900b 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -261,7 +261,6 @@ int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime) { CHECK_AND_INIT_MUTEX - UNREACHABLE(); return (*mutex)->Lock(abstime); } 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 +#include +#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" + +namespace Libraries::Np::NpWebApi { + +// Structs reference each other, so declare them before their contents. +struct OrbisNpWebApiContext; +struct OrbisNpWebApiUserContext; +struct OrbisNpWebApiRequest; +struct OrbisNpWebApiHandle; +struct OrbisNpWebApiTimerHandle; +struct OrbisNpWebApiPushEventFilter; +struct OrbisNpWebApiServicePushEventFilter; +struct OrbisNpWebApiExtendedPushEventFilter; +struct OrbisNpWebApiRegisteredPushEventCallback; +struct OrbisNpWebApiRegisteredServicePushEventCallback; +struct OrbisNpWebApiRegisteredExtendedPushEventCallback; + +struct OrbisNpWebApiContext { + s32 type; + s32 userCount; + s32 libCtxId; + s32 libHttpCtxId; + std::recursive_mutex contextLock; + std::map userContexts; + std::map handles; + std::map timerHandles; + std::map pushEventFilters; + std::map servicePushEventFilters; + std::map extendedPushEventFilters; + std::string name; + bool terminated; +}; + +struct OrbisNpWebApiUserContext { + OrbisNpWebApiContext* parentContext; + s32 userCount; + s32 userCtxId; + Libraries::UserService::OrbisUserServiceUserId userId; + std::map requests; + std::map pushEventCallbacks; + std::map servicePushEventCallbacks; + std::map extendedPushEventCallbacks; + bool deleted; + OrbisNpWebApiNotificationCallback notificationCallbackFunction; + void* pNotificationCallbackUserArgs; +}; + +struct OrbisNpWebApiRequest { + OrbisNpWebApiContext* parentContext; + s32 userCount; + s64 requestId; + std::string userApiGroup; + std::string userPath; + OrbisNpWebApiHttpMethod userMethod; + u64 userContentLength; + std::string userContentType; + bool multipart; + bool aborted; + bool sent; + u32 requestTimeout; + u64 requestEndTime; + bool timedOut; + // not sure Stephen + u8 requestState; + u64 remainingData; + u32 readOffset; + char data[64]; +}; + +struct OrbisNpWebApiHandle { + s32 handleId; + bool aborted; + bool deleted; + s32 userCount; +}; + +struct OrbisNpWebApiTimerHandle { + s32 handleId; + u32 handleTimeout; + u64 handleEndTime; + bool timedOut; +}; + +struct OrbisNpWebApiPushEventFilter { + s32 filterId; + std::vector filterParams; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiServicePushEventFilter { + s32 filterId; + bool internal; + std::vector filterParams; + std::string npServiceName; + OrbisNpServiceLabel npServiceLabel; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiExtendedPushEventFilter { + s32 filterId; + bool internal; + std::vector filterParams; + std::string npServiceName; + OrbisNpServiceLabel npServiceLabel; + OrbisNpWebApiContext* parentContext; +}; + +struct OrbisNpWebApiRegisteredPushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiPushEventCallback cbFunc; + void* pUserArg; +}; + +struct OrbisNpWebApiRegisteredServicePushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiServicePushEventCallback cbFunc; + OrbisNpWebApiInternalServicePushEventCallback internalCbFunc; + // Note: real struct stores both internal callbacks in one field + OrbisNpWebApiInternalServicePushEventCallbackA internalCbFuncA; + void* pUserArg; +}; + +struct OrbisNpWebApiRegisteredExtendedPushEventCallback { + s32 callbackId; + s32 filterId; + OrbisNpWebApiExtdPushEventCallback cbFunc; + // Note: real struct stores both callbacks in one field + OrbisNpWebApiExtdPushEventCallbackA cbFuncA; + void* pUserArg; +}; + +// General functions +s32 initializeLibrary(); // FUN_01001450 +s32 getCompiledSdkVersion(); // FUN_01001440 + +// Library context functions +s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name, + s32 type); // FUN_01006970 +OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag = 0); // FUN_01006860 +void releaseContext(OrbisNpWebApiContext* context); // FUN_01006fc0 +bool isContextTerminated(OrbisNpWebApiContext* context); // FUN_01006910 +bool isContextBusy(OrbisNpWebApiContext* context); // FUN_01008a50 +bool areContextHandlesBusy(OrbisNpWebApiContext* context); // FUN_01008c20 +void lockContext(OrbisNpWebApiContext* context); // FUN_010072e0 +void unlockContext(OrbisNpWebApiContext* context); // FUN_010072f0 +void markContextAsTerminated(OrbisNpWebApiContext* context); // FUN_01008bf0 +void checkContextTimeout(OrbisNpWebApiContext* context); // FUN_01008ad0 +void checkTimeout(); // FUN_01003700 +s32 deleteContext(s32 libCtxId); // FUN_01006c70 +s32 terminateContext(s32 libCtxId); // FUN_010014b0 + +// User context functions +OrbisNpWebApiUserContext* findUserContextByUserId( + OrbisNpWebApiContext* context, + Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010075c0 +OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context, + s32 userCtxId); // FUN_01007530 +s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId); // FUN_010016a0 +s32 createUserContext(s32 libCtxId, + Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010015c0 +s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc, + void* pUserArg); // FUN_01003770 +s32 unregisterNotificationCallback(s32 titleUserCtxId); // FUN_01003800 +bool isUserContextBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100ea40 +bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100d1f0 +void releaseUserContext(OrbisNpWebApiUserContext* userContext); // FUN_0100caa0 +void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext); // FUN_0100ea90 +s32 deleteUserContext(s32 userCtxId); // FUN_01001710 + +// Request functions +s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath, + OrbisNpWebApiHttpMethod method, + const OrbisNpWebApiContentParameter* pContentParameter, + const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId, + bool isMultipart); // FUN_01001850 +OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext, + s64 requestId); // FUN_0100d3a0 +OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext, + s64 requestId); // FUN_0100d330 +bool isRequestBusy(OrbisNpWebApiRequest* request); // FUN_0100c1b0 +s32 setRequestTimeout(s64 requestId, u32 timeout); // FUN_01003610 +void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0 +void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130 +s32 sendRequest( + s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag, + const OrbisNpWebApiResponseInformationOption* pResponseInformationOption); // FUN_01001c50 +s32 abortRequestInternal(OrbisNpWebApiContext* context, OrbisNpWebApiUserContext* userContext, + OrbisNpWebApiRequest* request); // FUN_01001b70 +s32 abortRequest(s64 requestId); // FUN_01002c70 +void releaseRequest(OrbisNpWebApiRequest* request); // FUN_01009fb0 +s32 deleteRequest(s64 requestId); // FUN_010019a0 + +// Handle functions +s32 createHandleInternal(OrbisNpWebApiContext* context); // FUN_01007730 +s32 createHandle(s32 libCtxId); // FUN_01002ee0 +s32 setHandleTimeoutInternal(OrbisNpWebApiContext* context, s32 handleId, + u32 timeout); // FUN_01007ed0 +s32 setHandleTimeout(s32 libCtxId, s32 handleId, u32 timeout); // FUN_010036b0 +void startHandleTimer(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007fd0 +void releaseHandle(OrbisNpWebApiContext* context, OrbisNpWebApiHandle* handle); // FUN_01007ea0 +s32 getHandle(OrbisNpWebApiContext* context, s32 handleId, + OrbisNpWebApiHandle** handleOut); // FUN_01007e20 +s32 abortHandle(s32 libCtxId, s32 handleId); // FUN_01003390 +s32 deleteHandleInternal(OrbisNpWebApiContext* context, s32 handleId); // FUN_01007a00 +s32 deleteHandle(s32 libCtxId, s32 handleId); // FUN_01002f20 + +// Push event filter functions +s32 createPushEventFilterInternal(OrbisNpWebApiContext* context, + const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01008040 +s32 createPushEventFilter(s32 libCtxId, const OrbisNpWebApiPushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01002d10 +s32 deletePushEventFilterInternal(OrbisNpWebApiContext* context, s32 filterId); // FUN_01008180 +s32 deletePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002d60 + +// Push event callback functions +s32 registerPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, + void* userArg); // FUN_0100d450 +s32 registerPushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiPushEventCallback cbFunc, + void* pUserArg); // FUN_01002da0 +s32 unregisterPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01002e50 + +// Service push event filter functions +s32 createServicePushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_010082f0 +s32 createServicePushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiServicePushEventFilterParameter* pFilterParam, + u64 filterParamNum); // FUN_01002f60 +s32 deleteServicePushEventFilterInternal(OrbisNpWebApiContext* context, + s32 filterId); // FUN_010084f0 +s32 deleteServicePushEventFilter(s32 libCtxId, s32 filterId); // FUN_01002fe0 + +// Service push event callback functions +s32 registerServicePushEventCallbackInternal( + OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, void* pUserArg); // FUN_0100d8c0 +s32 registerServicePushEventCallback(s32 titleUserCtxId, s32 filterId, + OrbisNpWebApiServicePushEventCallback cbFunc, + OrbisNpWebApiInternalServicePushEventCallback intCbFunc, + OrbisNpWebApiInternalServicePushEventCallbackA intCbFuncA, + void* pUserArg); // FUN_01003030 +s32 unregisterServicePushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_010030f0 + +// Extended push event filter functions +s32 createExtendedPushEventFilterInternal( + OrbisNpWebApiContext* context, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, u64 filterParamNum, + bool internal); // FUN_01008680 +s32 createExtendedPushEventFilter(s32 libCtxId, s32 handleId, const char* pNpServiceName, + OrbisNpServiceLabel npServiceLabel, + const OrbisNpWebApiExtdPushEventFilterParameter* pFilterParam, + u64 filterParamNum, bool internal); // FUN_01003180 +s32 deleteExtendedPushEventFilterInternal(OrbisNpWebApiContext* context, + s32 filterId); // FUN_01008880 +s32 deleteExtendedPushEventFilter(s32 libCtxId, s32 filterId); // FUN_01003200 + +// Extended push event callback functions +s32 registerExtdPushEventCallbackInternal(OrbisNpWebApiUserContext* userContext, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, + void* pUserArg); // FUN_0100df60 +s32 registerExtdPushEventCallback(s32 userCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallback cbFunc, + OrbisNpWebApiExtdPushEventCallbackA cbFuncA, + void* pUserArg); // FUN_01003250 +s32 registerExtdPushEventCallbackA(s32 userCtxId, s32 filterId, + OrbisNpWebApiExtdPushEventCallbackA cbFunc, + void* pUserArg); // FUN_01003240 +s32 unregisterExtdPushEventCallback(s32 titleUserCtxId, s32 callbackId); // FUN_01003300 + +s32 PS4_SYSV_ABI getHttpStatusCodeInternal(s64 requestId, s32* out_status_code); +s32 PS4_SYSV_ABI getHttpRequestIdFromRequest(OrbisNpWebApiRequest* request); +s32 PS4_SYSV_ABI readDataInternal(s64 requestId, void* pData, u64 size); +void PS4_SYSV_ABI setRequestEndTime(OrbisNpWebApiRequest* request); +void PS4_SYSV_ABI clearRequestEndTime(OrbisNpWebApiRequest* req); +bool PS4_SYSV_ABI hasRequestTimedOut(OrbisNpWebApiRequest* request); +void PS4_SYSV_ABI setRequestState(OrbisNpWebApiRequest* request, u8 state); +u64 PS4_SYSV_ABI copyRequestData(OrbisNpWebApiRequest* request, void* data, u64 size); + +}; // namespace Libraries::Np::NpWebApi \ No newline at end of file diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 90759c6cd..9d26142ce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" @@ -73,7 +73,7 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1 } u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { - static constexpr u64 MinSizeToClamp = 3_GB; + static constexpr u64 MinSizeToClamp = 1_GB; // Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer. if (size < MinSizeToClamp) { return size; @@ -349,7 +349,8 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) { } s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) { - std::scoped_lock lk{mutex, unmap_mutex}; + std::scoped_lock lk{unmap_mutex}; + std::unique_lock lk2{mutex}; ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}", virtual_addr); @@ -434,6 +435,7 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 // Merge this VMA with similar nearby areas MergeAdjacent(vma_map, new_vma_handle); + lk2.unlock(); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); } @@ -554,7 +556,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo } // Acquire writer lock. - std::scoped_lock lk2{mutex}; + std::unique_lock lk2{mutex}; // Create VMA representing this mapping. auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment); @@ -593,7 +595,10 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo // Tracy memory tracking breaks from merging memory areas. Disabled for now. // TRACK_ALLOC(out_addr, size_to_map, "VMEM"); + // Merge this handle with adjacent areas handle = MergeAdjacent(fmem_map, new_fmem_handle); + + // Get the next flexible area. current_addr += size_to_map; remaining_size -= size_to_map; flexible_usage += size_to_map; @@ -602,13 +607,13 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo ASSERT_MSG(remaining_size == 0, "Failed to map physical memory"); } else if (type == VMAType::Direct) { // Map the physical memory for this direct memory mapping. - auto phys_addr_to_search = phys_addr; + auto current_phys_addr = phys_addr; u64 remaining_size = size; auto dmem_area = FindDmemArea(phys_addr); while (dmem_area != dmem_map.end() && remaining_size > 0) { // Carve a new dmem area in place of this one with the appropriate type. // Ensure the carved area only covers the current dmem area. - const auto start_phys_addr = std::max(phys_addr, dmem_area->second.base); + const auto start_phys_addr = std::max(current_phys_addr, dmem_area->second.base); const auto offset_in_dma = start_phys_addr - dmem_area->second.base; const auto size_in_dma = std::min(dmem_area->second.size - offset_in_dma, remaining_size); @@ -617,17 +622,17 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo new_dmem_area.dma_type = PhysicalMemoryType::Mapped; // Add the dmem area to this vma, merge it with any similar tracked areas. - new_vma.phys_areas[phys_addr_to_search - phys_addr] = dmem_handle->second; - MergeAdjacent(new_vma.phys_areas, - new_vma.phys_areas.find(phys_addr_to_search - phys_addr)); + const u64 offset_in_vma = current_phys_addr - phys_addr; + new_vma.phys_areas[offset_in_vma] = dmem_handle->second; + MergeAdjacent(new_vma.phys_areas, new_vma.phys_areas.find(offset_in_vma)); // Merge the new dmem_area with dmem_map MergeAdjacent(dmem_map, dmem_handle); // Get the next relevant dmem area. - phys_addr_to_search = phys_addr + size_in_dma; + current_phys_addr += size_in_dma; remaining_size -= size_in_dma; - dmem_area = FindDmemArea(phys_addr_to_search); + dmem_area = FindDmemArea(current_phys_addr); } ASSERT_MSG(remaining_size == 0, "Failed to map physical memory"); } @@ -647,6 +652,8 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo // TRACK_ALLOC(mapped_addr, size, "VMEM"); } + lk2.unlock(); + // If this is not a reservation, then map to GPU and address space if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); diff --git a/src/core/module.h b/src/core/module.h index c39310406..778344e33 100644 --- a/src/core/module.h +++ b/src/core/module.h @@ -5,6 +5,7 @@ #include #include +#include "common/config.h" #include "common/types.h" #include "core/loader/elf.h" #include "core/loader/symbols_resolver.h" @@ -164,6 +165,14 @@ public: return elf.IsSharedLib(); } + bool IsSystemLib() { + auto system_path = Config::getSysModulesPath(); + if (file.string().starts_with(system_path.string().c_str())) { + return true; + } + return false; + } + template T GetProcParam() const noexcept { return reinterpret_cast(proc_param_virtual_addr); diff --git a/src/emulator.cpp b/src/emulator.cpp index 6ba80b096..0dde0b7fa 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -28,6 +28,7 @@ #include "common/singleton.h" #include "core/debugger.h" #include "core/devtools/widget/module_list.h" +#include "core/emulator_state.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -206,6 +207,13 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"), true); + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (id + ".toml"))) { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true); + } else { + EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false); + } + // Initialize logging as soon as possible if (!id.empty() && Config::getSeparateLogFilesEnabled()) { Common::Log::Initialize(id + ".log"); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index e74569737..6e5014c1b 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "input_handler.h" @@ -22,6 +22,8 @@ #include "common/elf_info.h" #include "common/io_file.h" #include "common/path_util.h" +#include "core/devtools/layer.h" +#include "core/emulator_state.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -108,6 +110,8 @@ auto output_array = std::array{ ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO), ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD), ControllerOutput(HOTKEY_RENDERDOC), + ControllerOutput(HOTKEY_VOLUME_UP), + ControllerOutput(HOTKEY_VOLUME_DOWN), ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), }; @@ -198,6 +202,8 @@ InputBinding GetBindingFromString(std::string& line) { input = InputID(InputType::Axis, string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); + } else if (string_to_hotkey_map.find(t) != string_to_hotkey_map.end()) { + input = InputID(InputType::Controller, string_to_hotkey_map.at(t)); } else { // Invalid token found; return default binding LOG_DEBUG(Input, "Invalid token found: {}", t); @@ -218,8 +224,8 @@ InputBinding GetBindingFromString(std::string& line) { void ParseInputConfig(const std::string game_id = "") { std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; - const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default); - const auto global_config_file = Config::GetFoolproofInputConfigFile("global"); + const auto config_file = Config::GetInputConfigFile(game_id_or_default); + const auto global_config_file = Config::GetInputConfigFile("global"); // we reset these here so in case the user fucks up or doesn't include some of these, // we can fall back to default @@ -392,19 +398,23 @@ void ParseInputConfig(const std::string game_id = "") { // normal cases InputBinding binding = GetBindingFromString(input_string); - BindingConnection connection(InputID(), nullptr); - auto button_it = string_to_cbutton_map.find(output_string); - auto axis_it = string_to_axis_map.find(output_string); if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); return; } + BindingConnection connection(InputID(), nullptr); + auto button_it = string_to_cbutton_map.find(output_string); + auto hotkey_it = string_to_hotkey_map.find(output_string); + auto axis_it = string_to_axis_map.find(output_string); if (button_it != string_to_cbutton_map.end()) { connection = BindingConnection( binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); connections.insert(connections.end(), connection); - + } else if (hotkey_it != string_to_hotkey_map.end()) { + connection = BindingConnection( + binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second))); + connections.insert(connections.end(), connection); } else if (axis_it != string_to_axis_map.end()) { int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( @@ -542,6 +552,7 @@ void ControllerOutput::FinalizeUpdate() { } old_button_state = new_button_state; old_param = *new_param; + bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed(); if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: @@ -562,6 +573,9 @@ void ControllerOutput::FinalizeUpdate() { case RIGHTJOYSTICK_HALFMODE: rightjoystick_halfmode = new_button_state; break; + case HOTKEY_RELOAD_INPUTS: + ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); + break; case HOTKEY_FULLSCREEN: PushSDLEvent(SDL_EVENT_TOGGLE_FULLSCREEN); break; @@ -571,9 +585,6 @@ void ControllerOutput::FinalizeUpdate() { case HOTKEY_SIMPLE_FPS: PushSDLEvent(SDL_EVENT_TOGGLE_SIMPLE_FPS); break; - case HOTKEY_RELOAD_INPUTS: - PushSDLEvent(SDL_EVENT_RELOAD_INPUTS); - break; case HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK: PushSDLEvent(SDL_EVENT_MOUSE_TO_JOYSTICK); break; @@ -586,6 +597,16 @@ void ControllerOutput::FinalizeUpdate() { case HOTKEY_RENDERDOC: PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); break; + case HOTKEY_VOLUME_UP: + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() + 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); + break; + case HOTKEY_VOLUME_DOWN: + Config::setVolumeSlider(std::clamp(Config::getVolumeSlider() - 10, 0, 500), + is_game_specific); + Overlay::ShowVolume(); + break; case HOTKEY_QUIT: PushSDLEvent(SDL_EVENT_QUIT_DIALOG); break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index eaadd164e..844870b5d 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -55,6 +55,8 @@ #define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007 #define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008 #define HOTKEY_RENDERDOC 0xf0000009 +#define HOTKEY_VOLUME_UP 0xf000000a +#define HOTKEY_VOLUME_DOWN 0xf000000b #define SDL_UNMAPPED UINT32_MAX - 1 @@ -136,6 +138,8 @@ const std::map string_to_cbutton_map = { {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, +}; +const std::map string_to_hotkey_map = { {"hotkey_pause", HOTKEY_PAUSE}, {"hotkey_fullscreen", HOTKEY_FULLSCREEN}, {"hotkey_show_fps", HOTKEY_SIMPLE_FPS}, @@ -145,6 +149,8 @@ const std::map string_to_cbutton_map = { {"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO}, {"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD}, {"hotkey_renderdoc_capture", HOTKEY_RENDERDOC}, + {"hotkey_volume_up", HOTKEY_VOLUME_UP}, + {"hotkey_volume_down", HOTKEY_VOLUME_DOWN}, }; const std::map string_to_axis_map = {