mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-27 05:17:10 -06:00
Merge branch 'shadps4-emu:main' into hle-libc-internal
This commit is contained in:
commit
2d93486bff
@ -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
|
||||
|
||||
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -38,6 +38,9 @@
|
||||
<category translate="no">Game</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="0.14.0" date="2026-02-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.14.0</url>
|
||||
</release>
|
||||
<release version="0.13.0" date="2025-12-24">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
|
||||
</release>
|
||||
|
||||
9
externals/CMakeLists.txt
vendored
9
externals/CMakeLists.txt
vendored
@ -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)
|
||||
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()
|
||||
|
||||
@ -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 <fstream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <fmt/core.h>
|
||||
@ -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<string, string> 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)) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@ -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<microseconds>(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<Entry> 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
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ struct Entry {
|
||||
std::string function;
|
||||
std::string message;
|
||||
std::string thread;
|
||||
u32 counter = 0;
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
||||
@ -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
|
||||
|
||||
@ -32,5 +32,6 @@ namespace Overlay {
|
||||
void ToggleSimpleFps();
|
||||
void SetSimpleFps(bool enabled);
|
||||
void ToggleQuitWindow();
|
||||
void ShowVolume();
|
||||
|
||||
} // namespace Overlay
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<EmulatorState> s_instance;
|
||||
@ -26,4 +28,5 @@ private:
|
||||
// state variables
|
||||
bool m_running = false;
|
||||
bool m_load_patches_auto = true;
|
||||
};
|
||||
bool m_game_specific_config_used = false;
|
||||
};
|
||||
|
||||
@ -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<u32>(m_frame_size, 4);
|
||||
return 4;
|
||||
}
|
||||
|
||||
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
@ -149,12 +150,11 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32*>(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<u32*>(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<u16>(info.encoder_delay),
|
||||
@ -163,6 +163,10 @@ DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& 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<const u16*>(p_fgh + 1));
|
||||
frame->total_samples = std::byteswap(*reinterpret_cast<const u32*>(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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +99,6 @@ private:
|
||||
SwrContext* m_swr_context = nullptr;
|
||||
std::optional<u32> m_header;
|
||||
u32 m_frame_samples = 0;
|
||||
u32 m_frame_size = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
||||
@ -1,23 +1,264 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <shared_mutex>
|
||||
#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<SDLAudioIn> audio = std::make_unique<SDLAudioIn>();
|
||||
std::array<std::shared_ptr<PortIn>, 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<AudioInBackend> 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<SDLAudioIn>();
|
||||
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<OrbisAudioInType>(type);
|
||||
OrbisAudioInParamFormat format = static_cast<OrbisAudioInParamFormat>(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<PortIn> port;
|
||||
try {
|
||||
port = std::make_shared<PortIn>();
|
||||
|
||||
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<u32>(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<u32>(in_type), len, freq, static_cast<u32>(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<PortIn> 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<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(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<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(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;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#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<PortInBackend> 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();
|
||||
|
||||
31
src/core/libraries/audio/audioin_backend.h
Normal file
31
src/core/libraries/audio/audioin_backend.h
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
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<PortInBackend> Open(PortIn& port) = 0;
|
||||
};
|
||||
|
||||
class SDLAudioIn final : public AudioInBackend {
|
||||
public:
|
||||
std::unique_ptr<PortInBackend> Open(PortIn& port) override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
20
src/core/libraries/audio/audioin_error.h
Normal file
20
src/core/libraries/audio/audioin_error.h
Normal file
@ -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;
|
||||
File diff suppressed because it is too large
Load Diff
@ -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<const u32*>(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<int, 8> 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<PortBackend> 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<s32, 8> 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);
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#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<int>(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<int>(guest_buffer_size))) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void SetVolume(const std::array<int, 8>& 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<float>(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<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#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<Uint8>(port.channels_num);
|
||||
fmt.freq = static_cast<int>(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<uint32_t>(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<u32>(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<PortInBackend> SDLAudioIn::Open(PortIn& port) {
|
||||
return std::make_unique<SDLInPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
600
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
600
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
@ -0,0 +1,600 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#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 <immintrin.h>
|
||||
#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<int, 8>& 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<float>(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<s64>(current_time - next_output_time);
|
||||
|
||||
if (time_diff > static_cast<s64>(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<u64>(-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<u8>(num_channels),
|
||||
.freq = static_cast<int>(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<int>(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<int> 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<const s16*>(src);
|
||||
float* d = static_cast<float*>(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<const s16*>(src);
|
||||
float* d = static_cast<float*>(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<const s16*>(src);
|
||||
float* d = static_cast<float*>(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<const __m128i*>(&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<const s16*>(src);
|
||||
float* d = static_cast<float*>(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<const s16*>(src);
|
||||
float* d = static_cast<float*>(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<const __m128i*>(&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<const float*>(src);
|
||||
float* d = static_cast<float*>(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<int, 8> channel_layout;
|
||||
|
||||
alignas(64) u64 period_us{0};
|
||||
alignas(64) std::atomic<u64> 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<float> current_gain{1.0f};
|
||||
|
||||
// SDL audio stream
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
u32 queue_threshold{0};
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
@ -1,139 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#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<int>(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<uint32_t>(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<int>(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 = {};
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
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<AudioInPort, 8> portsIn;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#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<OrbisKernelTimespec*>(&filestat.st_atim);
|
||||
sb->st_mtim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_mtim);
|
||||
sb->st_ctim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_ctim);
|
||||
#elif defined(__APPLE__)
|
||||
struct stat filestat = {};
|
||||
stat(file->f.GetPath().c_str(), &filestat);
|
||||
sb->st_atim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_atimespec);
|
||||
sb->st_mtim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_mtimespec);
|
||||
sb->st_ctim = *reinterpret_cast<OrbisKernelTimespec*>(&filestat.st_ctimespec);
|
||||
#else
|
||||
const auto ft = std::filesystem::last_write_time(file->f.GetPath());
|
||||
const auto sctp = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||
ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
const auto secs = std::chrono::time_point_cast<std::chrono::seconds>(sctp);
|
||||
const auto nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(sctp - secs);
|
||||
|
||||
sb->st_mtim.tv_sec = static_cast<int64_t>(secs.time_since_epoch().count());
|
||||
sb->st_mtim.tv_nsec = static_cast<int64_t>(nsecs.count());
|
||||
sb->st_atim = sb->st_mtim;
|
||||
sb->st_ctim = sb->st_mtim;
|
||||
#endif
|
||||
// TODO incomplete
|
||||
break;
|
||||
}
|
||||
|
||||
@ -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<Core::Linker>::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<Core::Linker>::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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
@ -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 <magic_enum/magic_enum.hpp>
|
||||
|
||||
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() {
|
||||
|
||||
@ -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
|
||||
36
src/core/libraries/np/np_web_api_error.h
Normal file
36
src/core/libraries/np/np_web_api_error.h
Normal file
@ -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;
|
||||
1534
src/core/libraries/np/np_web_api_internal.cpp
Normal file
1534
src/core/libraries/np/np_web_api_internal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
301
src/core/libraries/np/np_web_api_internal.h
Normal file
301
src/core/libraries/np/np_web_api_internal.h
Normal file
@ -0,0 +1,301 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#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<s32, OrbisNpWebApiUserContext*> userContexts;
|
||||
std::map<s32, OrbisNpWebApiHandle*> handles;
|
||||
std::map<s32, OrbisNpWebApiTimerHandle*> timerHandles;
|
||||
std::map<s32, OrbisNpWebApiPushEventFilter*> pushEventFilters;
|
||||
std::map<s32, OrbisNpWebApiServicePushEventFilter*> servicePushEventFilters;
|
||||
std::map<s32, OrbisNpWebApiExtendedPushEventFilter*> extendedPushEventFilters;
|
||||
std::string name;
|
||||
bool terminated;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiUserContext {
|
||||
OrbisNpWebApiContext* parentContext;
|
||||
s32 userCount;
|
||||
s32 userCtxId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
std::map<s64, OrbisNpWebApiRequest*> requests;
|
||||
std::map<s32, OrbisNpWebApiRegisteredPushEventCallback*> pushEventCallbacks;
|
||||
std::map<s32, OrbisNpWebApiRegisteredServicePushEventCallback*> servicePushEventCallbacks;
|
||||
std::map<s32, OrbisNpWebApiRegisteredExtendedPushEventCallback*> extendedPushEventCallbacks;
|
||||
bool deleted;
|
||||
OrbisNpWebApiNotificationCallback notificationCallbackFunction;
|
||||
void* pNotificationCallbackUserArgs;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiRequest {
|
||||
OrbisNpWebApiContext* parentContext;
|
||||
s32 userCount;
|
||||
s64 requestId;
|
||||
std::string userApiGroup;
|
||||
std::string userPath;
|
||||
OrbisNpWebApiHttpMethod userMethod;
|
||||
u64 userContentLength;
|
||||
std::string userContentType;
|
||||
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<OrbisNpWebApiPushEventFilterParameter> filterParams;
|
||||
OrbisNpWebApiContext* parentContext;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiServicePushEventFilter {
|
||||
s32 filterId;
|
||||
bool internal;
|
||||
std::vector<OrbisNpWebApiServicePushEventFilterParameter> filterParams;
|
||||
std::string npServiceName;
|
||||
OrbisNpServiceLabel npServiceLabel;
|
||||
OrbisNpWebApiContext* parentContext;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiExtendedPushEventFilter {
|
||||
s32 filterId;
|
||||
bool internal;
|
||||
std::vector<OrbisNpWebApiExtdPushEventFilterParameter> filterParams;
|
||||
std::string npServiceName;
|
||||
OrbisNpServiceLabel npServiceLabel;
|
||||
OrbisNpWebApiContext* parentContext;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiRegisteredPushEventCallback {
|
||||
s32 callbackId;
|
||||
s32 filterId;
|
||||
OrbisNpWebApiPushEventCallback cbFunc;
|
||||
void* pUserArg;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiRegisteredServicePushEventCallback {
|
||||
s32 callbackId;
|
||||
s32 filterId;
|
||||
OrbisNpWebApiServicePushEventCallback cbFunc;
|
||||
OrbisNpWebApiInternalServicePushEventCallback internalCbFunc;
|
||||
// Note: real struct stores both internal callbacks in one field
|
||||
OrbisNpWebApiInternalServicePushEventCallbackA internalCbFuncA;
|
||||
void* pUserArg;
|
||||
};
|
||||
|
||||
struct OrbisNpWebApiRegisteredExtendedPushEventCallback {
|
||||
s32 callbackId;
|
||||
s32 filterId;
|
||||
OrbisNpWebApiExtdPushEventCallback cbFunc;
|
||||
// Note: real struct stores both callbacks in one field
|
||||
OrbisNpWebApiExtdPushEventCallbackA cbFuncA;
|
||||
void* pUserArg;
|
||||
};
|
||||
|
||||
// General functions
|
||||
s32 initializeLibrary(); // FUN_01001450
|
||||
s32 getCompiledSdkVersion(); // FUN_01001440
|
||||
|
||||
// Library context functions
|
||||
s32 createLibraryContext(s32 libHttpCtxId, u64 poolSize, const char* name,
|
||||
s32 type); // FUN_01006970
|
||||
OrbisNpWebApiContext* findAndValidateContext(s32 libCtxId, s32 flag = 0); // FUN_01006860
|
||||
void releaseContext(OrbisNpWebApiContext* context); // FUN_01006fc0
|
||||
bool isContextTerminated(OrbisNpWebApiContext* context); // FUN_01006910
|
||||
bool isContextBusy(OrbisNpWebApiContext* context); // FUN_01008a50
|
||||
bool areContextHandlesBusy(OrbisNpWebApiContext* context); // FUN_01008c20
|
||||
void lockContext(OrbisNpWebApiContext* context); // FUN_010072e0
|
||||
void unlockContext(OrbisNpWebApiContext* context); // FUN_010072f0
|
||||
void markContextAsTerminated(OrbisNpWebApiContext* context); // FUN_01008bf0
|
||||
void checkContextTimeout(OrbisNpWebApiContext* context); // FUN_01008ad0
|
||||
void checkTimeout(); // FUN_01003700
|
||||
s32 deleteContext(s32 libCtxId); // FUN_01006c70
|
||||
s32 terminateContext(s32 libCtxId); // FUN_010014b0
|
||||
|
||||
// User context functions
|
||||
OrbisNpWebApiUserContext* findUserContextByUserId(
|
||||
OrbisNpWebApiContext* context,
|
||||
Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010075c0
|
||||
OrbisNpWebApiUserContext* findUserContext(OrbisNpWebApiContext* context,
|
||||
s32 userCtxId); // FUN_01007530
|
||||
s32 createUserContextWithOnlineId(s32 libCtxId, OrbisNpOnlineId* onlineId); // FUN_010016a0
|
||||
s32 createUserContext(s32 libCtxId,
|
||||
Libraries::UserService::OrbisUserServiceUserId userId); // FUN_010015c0
|
||||
s32 registerNotificationCallback(s32 titleUserCtxId, OrbisNpWebApiNotificationCallback cbFunc,
|
||||
void* pUserArg); // FUN_01003770
|
||||
s32 unregisterNotificationCallback(s32 titleUserCtxId); // FUN_01003800
|
||||
bool isUserContextBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100ea40
|
||||
bool areUserContextRequestsBusy(OrbisNpWebApiUserContext* userContext); // FUN_0100d1f0
|
||||
void releaseUserContext(OrbisNpWebApiUserContext* userContext); // FUN_0100caa0
|
||||
void checkUserContextTimeout(OrbisNpWebApiUserContext* userContext); // FUN_0100ea90
|
||||
s32 deleteUserContext(s32 userCtxId); // FUN_01001710
|
||||
|
||||
// Request functions
|
||||
s32 createRequest(s32 titleUserCtxId, const char* pApiGroup, const char* pPath,
|
||||
OrbisNpWebApiHttpMethod method,
|
||||
const OrbisNpWebApiContentParameter* pContentParameter,
|
||||
const OrbisNpWebApiIntCreateRequestExtraArgs* pInternalArgs, s64* pRequestId,
|
||||
bool isMultipart); // FUN_01001850
|
||||
OrbisNpWebApiRequest* findRequest(OrbisNpWebApiUserContext* userContext,
|
||||
s64 requestId); // FUN_0100d3a0
|
||||
OrbisNpWebApiRequest* findRequestAndMarkBusy(OrbisNpWebApiUserContext* userContext,
|
||||
s64 requestId); // FUN_0100d330
|
||||
bool isRequestBusy(OrbisNpWebApiRequest* request); // FUN_0100c1b0
|
||||
s32 setRequestTimeout(s64 requestId, u32 timeout); // FUN_01003610
|
||||
void startRequestTimer(OrbisNpWebApiRequest* request); // FUN_0100c0d0
|
||||
void checkRequestTimeout(OrbisNpWebApiRequest* request); // FUN_0100c130
|
||||
s32 sendRequest(
|
||||
s64 requestId, s32 partIndex, const void* data, u64 dataSize, s8 flag,
|
||||
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
|
||||
@ -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<PAddr>(phys_addr, dmem_area->second.base);
|
||||
const auto start_phys_addr = std::max<PAddr>(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<u64>(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);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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 <typename T = VAddr>
|
||||
T GetProcParam() const noexcept {
|
||||
return reinterpret_cast<T>(proc_param_virtual_addr);
|
||||
|
||||
@ -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<std::string> 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");
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<std::string, u32> 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<std::string, u32> 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<std::string, u32> 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<std::string, AxisMapping> string_to_axis_map = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user