Merge remote-tracking branch 'origin/main' into user_and_settings

This commit is contained in:
kalaposfos13 2026-02-12 22:31:24 +01:00
commit 1472781784
105 changed files with 9364 additions and 1827 deletions

View File

@ -3,7 +3,16 @@
name: Build and Release
on: [push, pull_request]
on:
push:
paths-ignore:
- "documents/**"
- "**/*.md"
pull_request:
paths-ignore:
- "documents/**"
- "**/*.md"
concurrency:
group: ci-${{ github.event_name }}-${{ github.ref }}

15
.gitmodules vendored
View File

@ -2,10 +2,6 @@
path = externals/zlib-ng
url = https://github.com/shadps4-emu/ext-zlib-ng.git
shallow = true
[submodule "externals/sdl3"]
path = externals/sdl3
url = https://github.com/shadps4-emu/ext-SDL.git
shallow = true
[submodule "externals/fmt"]
path = externals/fmt
url = https://github.com/shadps4-emu/ext-fmt.git
@ -123,7 +119,10 @@
[submodule "externals/aacdec/fdk-aac"]
path = externals/aacdec/fdk-aac
url = https://android.googlesource.com/platform/external/aac
[submodule "externals/ext-CLI11"]
path = externals/ext-CLI11
url = https://github.com/shadexternals/ext-CLI11.git
branch = main
[submodule "externals/CLI11"]
path = externals/CLI11
url = https://github.com/shadexternals/CLI11.git
[submodule "externals/sdl3"]
path = externals/sdl3
url = https://github.com/shadexternals/sdl3.git

View File

@ -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
)
@ -494,6 +496,8 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp
src/core/libraries/libc_internal/libc_internal_str.h
src/core/libraries/libc_internal/libc_internal_math.cpp
src/core/libraries/libc_internal/libc_internal_math.h
src/core/libraries/libc_internal/libc_internal_threads.cpp
src/core/libraries/libc_internal/libc_internal_threads.h
src/core/libraries/libc_internal/printf.h
)
@ -524,6 +528,9 @@ set(SYSTEM_GESTURE_LIB
set(PNG_LIB src/core/libraries/libpng/pngdec.cpp
src/core/libraries/libpng/pngdec.h
src/core/libraries/libpng/pngdec_error.h
src/core/libraries/libpng/pngenc.cpp
src/core/libraries/libpng/pngenc.h
src/core/libraries/libpng/pngenc_error.h
)
set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h
@ -581,14 +588,21 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_commerce.h
src/core/libraries/np/np_manager.cpp
src/core/libraries/np/np_manager.h
src/core/libraries/np/np_matching2.cpp
src/core/libraries/np/np_matching2.h
src/core/libraries/np/np_score.cpp
src/core/libraries/np/np_score.h
src/core/libraries/np/np_trophy.cpp
src/core/libraries/np/np_trophy.h
src/core/libraries/np/np_tus.cpp
src/core/libraries/np/np_tus.h
src/core/libraries/np/trophy_ui.cpp
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
@ -599,6 +613,9 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_profile_dialog.h
src/core/libraries/np/np_sns_facebook_dialog.cpp
src/core/libraries/np/np_sns_facebook_dialog.h
src/core/libraries/np/np_partner.cpp
src/core/libraries/np/np_partner.h
src/core/libraries/np/object_manager.h
)
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp

View File

@ -20,6 +20,7 @@ path = [
"documents/Quickstart/2.png",
"documents/Screenshots/*",
"documents/Screenshots/Linux/*",
"documents/Screenshots/Windows/*",
"externals/MoltenVK/MoltenVK_icd.json",
"scripts/ps4_names.txt",
"src/images/bronze.png",

View File

@ -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>

View File

@ -17,16 +17,19 @@
"customizations": {
"vscode": {
"extensions": [
"llvm-vs-code-extensions.vscode-clangd"
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"xaver.clang-format"
],
"settings": {
"C_Cpp.intelliSenseEngine": "disabled",
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--header-insertion=never"
]
"--header-insertion=never",
"--compile-commands-dir=/workspaces/shadPS4/Build/x64-Clang-Release"
],
"C_Cpp.intelliSenseEngine": "Disabled"
}
}
},
@ -37,9 +40,12 @@
"CC": "clang",
"CXX": "clang++"
},
"cmake.configureSettings": {
"cmake.configureEnvironment": {
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
},
"editor.formatOnSave": true,
"clang-format.executable": "clang-format-19"
}
}

View File

@ -1,38 +1,45 @@
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
FROM ubuntu:24.04
FROM archlinux:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN pacman-key --init && \
pacman-key --populate archlinux && \
pacman -Syu --noconfirm
RUN apt-get update && apt-get install -y \
build-essential \
RUN pacman -S --noconfirm \
base-devel \
clang \
clang19 \
ninja \
git \
ca-certificates \
wget \
libasound2-dev \
libpulse-dev \
libopenal-dev \
libssl-dev \
zlib1g-dev \
libedit-dev \
libudev-dev \
libevdev-dev \
libsdl2-dev \
libjack-dev \
libsndio-dev \
libxtst-dev \
libvulkan-dev \
vulkan-validationlayers \
libpng-dev \
clang-tidy \
&& rm -rf /var/lib/apt/lists/*
RUN wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor -o /usr/share/keyrings/kitware-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ noble main" > /etc/apt/sources.list.d/kitware.list \
&& apt-get update \
&& apt-get install -y cmake \
&& rm -rf /var/lib/apt/lists/*/*
alsa-lib \
libpulse \
openal \
openssl \
zlib \
libedit \
systemd-libs \
libevdev \
sdl2 \
jack \
sndio \
libxtst \
vulkan-headers \
vulkan-validation-layers \
libpng \
clang-tools-extra \
cmake \
libx11 \
libxrandr \
libxcursor \
libxi \
libxinerama \
libxss \
&& pacman -Scc --noconfirm
RUN ln -sf /usr/lib/llvm19/bin/clang-format /usr/bin/clang-format-19
WORKDIR /workspaces/shadPS4

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -54,7 +54,16 @@ or your fork link.
git submodule update --init --recursive
```
## Step 3: Build with CMake
## Step 3: Build with CMake Tools (GUI)
Generate build with CMake Tools.
1. Go `CMake Tools > Configure > '>'`
2. And `Build > '>'`
Compiled executable in `Build` folder.
## Alternative Step 3: Build with CMake
Generate the build directory and configure the project using Clang:

View File

@ -41,10 +41,171 @@ Go through the Git for Windows installation as normal
Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\`
## Option 2: MSYS2/MinGW
## Option 2: VSCode with Visual Studio Build Tools
If your default IDE is VSCode, we have a fully functional example for that as well.
### Requirements
* [**Git for Windows**](https://git-scm.com/download/win)
* [**LLVM 19.1.1**](https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.1/LLVM-19.1.1-win64.exe)
* [**CMake 4.2.3 or newer**](https://github.com/Kitware/CMake/releases/download/v4.2.3/cmake-4.2.3-windows-x86_64.msi)
* [**Ninja 1.13.2 or newer**](https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-win.zip)
**The main reason we use clang19 is because that version is used in CI for formatting.**
### Installs
1. Go through the Git for Windows installation as normal
2. Download and Run LLVM Installer and `Add LLVM to the system PATH for all users`
3. Download and Run CMake Installer and `Add CMake to the system PATH for all users`
4. Download Ninja and extract it to `C:\ninja` and add it to the system PATH for all users
* You can do this by going to `Search with Start Menu -> Environment Variables -> System Variables -> Path -> Edit -> New -> C:\ninja`
### Validate the installs
```bash
git --version
# git version 2.49.0.windows.1
cmake --version
# cmake version 4.2.3
ninja --version
# 1.13.2
clang --version
# clang version 19.1.1
```
### Install Visual Studio Build Tools
1. Download [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
2. Select `MSVC - Windows SDK` and install (you don't need to install an IDE)
* Or you can install via `.vsconfig` file:
```
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Component.CoreBuildTools",
"Microsoft.VisualStudio.Workload.MSBuildTools",
"Microsoft.VisualStudio.Component.Windows10SDK",
"Microsoft.VisualStudio.Component.VC.CoreBuildTools",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.Component.Windows11SDK.26100",
"Microsoft.VisualStudio.Component.TestTools.BuildTools",
"Microsoft.VisualStudio.Component.VC.ASAN",
"Microsoft.VisualStudio.Component.TextTemplating",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
"Microsoft.VisualStudio.Workload.VCTools"
],
"extensions": []
}
Save the file as `.vsconfig` and run the following command:
%userprofile%\Downloads\vs_BuildTools.exe --passive --config ".vsconfig"
Be carefull path to vs_BuildTools.exe and .vsconfig file.
```
__This will install the necessary components to build shadPS4.__
### Project structure
```
shadps4/
├── shared (shadps4 main files)
└── shadps4.code-workspace
```
### Content of `shadps4.code-workspace`
```json
{
"folders": [
{
"path": "shared"
}
],
"settings": {
"cmake.generator": "Ninja",
"cmake.configureEnvironment": {
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
},
"cmake.configureOnOpen": false,
"C_Cpp.intelliSenseEngine": "Disabled",
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--header-insertion=never",
"--compile-commands-dir=Build/x64-Clang-Release"
],
"editor.formatOnSave": true,
"clang-format.executable": "clang-format"
},
"extensions": {
"recommendations": [
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"xaver.clang-format"
]
}
}
```
### Cloning the source code
1. Open your terminal and where to shadPS4 folder: `cd shadps4\shared`
3. Clone the repository by running
`git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4 .`
_or fork link_
* If you have already cloned repo:
```bash
git submodule update --init --recursive
```
### Requirements VSCode extensions
1. CMake Tools
2. Clangd
3. Clang-Format
_These plugins are suggested in the workspace file above and are already configured._
![CMake Tools](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-1.png)
![Clangd](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-2.png)
![Clang Format](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/windows/vscode-ext-3.png)
### Building
1. Open VS Code, `File > Open workspace from file > shadps4.code-workspace`
2. Go to the CMake Tools extension on left side bar
3. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build.
4. Click build.
Your shadps4.exe will be in `shadps4\shared\Build\x64-Clang-Release\`
## Option 3: MSYS2/MinGW
> [!IMPORTANT]
> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022).
> Building with MSYS2 is broken as of right now, the only way to build on Windows is to use [Option 1: Visual Studio 2022](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md#option-1-visual-studio-2022) or [Option 2: VSCode with Visual Studio Build Tools](#option-2-vscode-with-visual-studio-build-tools).
### (Prerequisite) Download [**MSYS2**](https://www.msys2.org/)

1
externals/CLI11 vendored Submodule

@ -0,0 +1 @@
Subproject commit bf5a16a26a34a9a7ad75f4a7705585e44675fef0

View File

@ -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(ext-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()

2
externals/MoltenVK vendored

@ -1 +1 @@
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030

1
externals/ext-CLI11 vendored

@ -1 +0,0 @@
Subproject commit 1cce1483345e60997b87720948c37d6a34db2658

View File

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 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;
@ -346,18 +349,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_add_virtual_user = f5
hotkey_remove_virtual_user = f4
hotkey_toggle_mouse_to_touchpad = delete
hotkey_quit = lctrl, lshift, end
)";
}
@ -435,7 +426,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.
@ -474,6 +465,41 @@ 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_add_virtual_user", "f5"},
{"hotkey_remove_virtual_user", "f4"},
{"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)) {

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -41,6 +41,6 @@ void SetControllerCustomColor(int r, int b, int g);
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

View File

@ -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;

View File

@ -4,6 +4,7 @@
#include <chrono>
#include <filesystem>
#include <mutex>
#include <thread>
#include <fmt/format.h>
@ -210,26 +211,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 (EmulatorSettings::GetInstance()->GetLogType() == "async") {
message_queue.EmplaceWait(entry);
} else {
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });
std::fflush(stdout);
}
}
private:
@ -261,6 +277,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();
@ -294,6 +326,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

View File

@ -107,12 +107,15 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, NpCommon) \
SUB(Lib, NpCommerce) \
SUB(Lib, NpManager) \
SUB(Lib, NpMatching2) \
SUB(Lib, NpScore) \
SUB(Lib, NpTrophy) \
SUB(Lib, NpTus) \
SUB(Lib, NpWebApi) \
SUB(Lib, NpWebApi2) \
SUB(Lib, NpProfileDialog) \
SUB(Lib, NpSnsFacebookDialog) \
SUB(Lib, NpPartner) \
SUB(Lib, Screenshot) \
SUB(Lib, LibCInternal) \
SUB(Lib, AppContent) \

View File

@ -22,6 +22,7 @@ struct Entry {
std::string function;
std::string message;
std::string thread;
u32 counter = 0;
};
} // namespace Common::Log

View File

@ -74,8 +74,10 @@ enum class Class : u8 {
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
Lib_NpAuth, ///< The LibSceNpAuth implementation
Lib_NpManager, ///< The LibSceNpManager implementation
Lib_NpMatching2, ///< The LibSceNpMatching2 implementation
Lib_NpScore, ///< The LibSceNpScore implementation
Lib_NpTrophy, ///< The LibSceNpTrophy implementation
Lib_NpTus, ///< The LibSceNpTus implementation
Lib_NpWebApi, ///< The LibSceWebApi implementation
Lib_NpWebApi2, ///< The LibSceWebApi2 implementation
Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation
@ -110,6 +112,7 @@ enum class Class : u8 {
Lib_Mouse, ///< The LibSceMouse implementation
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
Lib_NpParty, ///< The LibSceNpParty implementation
Lib_NpPartner, ///< The LibSceNpPartner implementation
Lib_Zlib, ///< The LibSceZlib implementation.
Lib_Hmd, ///< The LibSceHmd implementation.
Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation.

View File

@ -17,6 +17,15 @@ public:
writer_active = true;
}
bool try_lock() {
std::lock_guard<std::mutex> lock(mtx);
if (writer_active || readers > 0) {
return false;
}
writer_active = true;
return true;
}
void unlock() {
std::lock_guard<std::mutex> lock(mtx);
writer_active = false;

View File

@ -174,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = name;
}
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
}
@ -186,6 +189,9 @@ void SetThreadName(void* thread, const char* name) {
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = name;
}
#ifdef __APPLE__
pthread_setname_np(name);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
@ -212,6 +218,9 @@ void SetThreadName(void* thread, const char* name) {
#if defined(_WIN32)
void SetCurrentThreadName(const char*) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = name;
}
// Do Nothing on MinGW
}

View File

@ -710,7 +710,7 @@ struct AddressSpace::Impl {
return ret;
}
void Unmap(VAddr virtual_addr, u64 size, bool) {
void Unmap(VAddr virtual_addr, u64 size) {
// Check to see if we are adjacent to any regions.
VAddr start_address = virtual_addr;
VAddr end_address = start_address + size;
@ -793,12 +793,8 @@ void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot,
#endif
}
void AddressSpace::Unmap(VAddr virtual_addr, u64 size, bool has_backing) {
#ifdef _WIN32
void AddressSpace::Unmap(VAddr virtual_addr, u64 size) {
impl->Unmap(virtual_addr, size);
#else
impl->Unmap(virtual_addr, size, has_backing);
#endif
}
void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) {

View File

@ -79,8 +79,9 @@ public:
void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd);
/// Unmaps specified virtual memory area.
void Unmap(VAddr virtual_addr, u64 size, bool has_backing);
void Unmap(VAddr virtual_addr, u64 size);
/// Protects requested region.
void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms);
// Returns an interval set containing all usable regions.

View File

@ -33,6 +33,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;
@ -455,6 +458,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();
}
@ -483,4 +507,9 @@ void ToggleQuitWindow() {
show_quit_window = !show_quit_window;
}
void ShowVolume() {
volume_start_time = ImGui::GetTime();
show_volume = true;
}
} // namespace Overlay

View File

@ -32,5 +32,6 @@ namespace Overlay {
void ToggleSimpleFps();
void SetSimpleFps(bool enabled);
void ToggleQuitWindow();
void ShowVolume();
} // namespace Overlay

View File

@ -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;
}

View File

@ -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;
};

View File

@ -232,6 +232,9 @@ File* HandleTable::GetSocket(int d) {
return nullptr;
}
auto file = m_files.at(d);
if (!file) {
return nullptr;
}
if (file->type != Core::FileSys::FileType::Socket) {
return nullptr;
}

View File

@ -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.");
}
}

View File

@ -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

View File

@ -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;

View File

@ -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();

View 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

View 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

View File

@ -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);

View File

@ -1,158 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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/emulator_settings.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
? EmulatorSettings::GetInstance()->GetPadSpkOutputDevice()
: EmulatorSettings::GetInstance()->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, EmulatorSettings::GetInstance()->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 *
EmulatorSettings::GetInstance()->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

View 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

View 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

View File

@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <common/config.h>
#include <common/logging/log.h>
#include "core/emulator_settings.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 = EmulatorSettings::GetInstance()->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 = {};
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
}
@ -1262,7 +1287,8 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri
if (file->type == Core::FileSys::FileType::Regular ||
file->type == Core::FileSys::FileType::Device) {
// Disk files always ready
if (want_read) {
// For devices, stdin (fd 0) is never read-ready.
if (want_read && i != 0) {
FD_SET_POSIX(i, &read_ready);
}
if (want_write) {

View File

@ -65,6 +65,9 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000;
constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000;
constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000;
s32 PS4_SYSV_ABI posix_open(const char* path, s32 flags, u16 mode);
s32 PS4_SYSV_ABI posix_close(s32 fd);
s64 PS4_SYSV_ABI posix_lseek(s32 fd, s64 offset, s32 whence);
s64 PS4_SYSV_ABI sceKernelWrite(s32 fd, const void* buf, u64 nbytes);
s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes);
s64 PS4_SYSV_ABI sceKernelPread(s32 fd, void* buf, u64 nbytes, s64 offset);

View File

@ -192,6 +192,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;
@ -231,6 +251,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;
@ -250,8 +295,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);
}

View File

@ -28,6 +28,16 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return);
int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr);
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type);
int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr);
int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr,
const char* name);
int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex);
int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex);
int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex);
void RegisterThreads(Core::Loader::SymbolsResolver* sym);
class Thread {

View File

@ -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);
}
@ -378,7 +377,8 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) {
}
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) {
if (attr == nullptr || *attr == nullptr || type >= PthreadMutexType::Max) {
if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck ||
type >= PthreadMutexType::Max) {
return POSIX_EINVAL;
}
(*attr)->m_type = type;

View 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
#include "common/assert.h"
@ -14,12 +14,6 @@
namespace Libraries::Kernel {
constexpr int PthreadInheritSched = 4;
constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
extern PthreadAttr PthreadAttrDefault;
void _thread_cleanupspecific();
@ -231,7 +225,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
new_thread->attr = *(*attr);
new_thread->attr.cpusetsize = 0;
}
if (new_thread->attr.sched_inherit == PthreadInheritSched) {
if (curthread != nullptr && new_thread->attr.sched_inherit == PthreadInheritSched) {
if (True(curthread->attr.flags & PthreadAttrFlags::ScopeSystem)) {
new_thread->attr.flags |= PthreadAttrFlags::ScopeSystem;
} else {
@ -325,6 +319,7 @@ PthreadT PS4_SYSV_ABI posix_pthread_self() {
}
void PS4_SYSV_ABI posix_pthread_set_name_np(PthreadT thread, const char* name) {
LOG_INFO(Kernel_Pthread, "called, new name: {}", name);
Common::SetCurrentThreadName(name);
}

View File

@ -24,6 +24,12 @@ class SymbolsResolver;
namespace Libraries::Kernel {
constexpr int PthreadInheritSched = 4;
constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
struct Pthread;
enum class PthreadMutexFlags : u32 {

View File

@ -24,9 +24,9 @@ static constexpr std::array<PthreadPrio, 3> ThrPriorities = {{
}};
PthreadAttr PthreadAttrDefault = {
.sched_policy = SchedPolicy::Fifo,
.sched_inherit = 0,
.prio = 0,
.sched_policy = SchedPolicy::Other,
.sched_inherit = PthreadInheritSched,
.prio = ORBIS_KERNEL_PRIO_FIFO_DEFAULT,
.suspend = false,
.flags = PthreadAttrFlags::ScopeSystem,
.stackaddr_attr = nullptr,

View File

@ -11,6 +11,7 @@
#include "libc_internal_math.h"
#include "libc_internal_memory.h"
#include "libc_internal_str.h"
#include "libc_internal_threads.h"
#include "printf.h"
namespace Libraries::LibcInternal {
@ -20,5 +21,11 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
RegisterlibSceLibcInternalStr(sym);
RegisterlibSceLibcInternalMemory(sym);
RegisterlibSceLibcInternalIo(sym);
RegisterlibSceLibcInternalThreads(sym);
}
void ForceRegisterLib(Core::Loader::SymbolsResolver* sym) {
// Used to forcibly enable HLEs for broken LLE functions.
ForceRegisterlibSceLibcInternalIo(sym);
}
} // namespace Libraries::LibcInternal

View File

@ -15,4 +15,5 @@ namespace Libraries::LibcInternal {
// so everything is just in the .cpp file
void RegisterLib(Core::Loader::SymbolsResolver* sym);
void ForceRegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::LibcInternal

View File

@ -3,21 +3,484 @@
#include <cstdarg>
#include <cstdio>
#include <map>
#include <common/va_ctx.h>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/libc_internal/libc_internal_io.h"
#include "core/libraries/libc_internal/libc_internal_threads.h"
#include "core/libraries/libs.h"
#include "libc_internal_io.h"
#include "printf.h"
namespace Libraries::LibcInternal {
int PS4_SYSV_ABI internal_snprintf(char* s, size_t n, VA_ARGS) {
s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS) {
VA_CTX(ctx);
return snprintf_ctx(s, n, &ctx);
}
std::map<s32, OrbisFILE*> g_files{};
// Constants for tracking accurate file indexes.
// Since the file struct is exposed to the application, accuracy is important.
static constexpr s32 g_initial_files = 5;
static constexpr s32 g_max_files = 0x100;
OrbisFILE* PS4_SYSV_ABI internal__Fofind() {
u64 index = g_initial_files;
while (index != g_max_files) {
OrbisFILE* file = g_files[index];
// If file doesn't exist, create it.
if (file == nullptr) {
file = new OrbisFILE();
if (file == nullptr) {
return nullptr;
}
// Store new file in the array, initialize default values, and return it.
g_files[index] = file;
file->_Mode = 0x80;
file->_Idx = index;
return file;
}
// Special case, files with mode 0 are returned?
if (file->_Mode == 0) {
file->_Mode = 0xff7f;
return file;
}
index++;
}
return nullptr;
}
void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file) {
if (file != nullptr && file->_Mutex != nullptr) {
internal__Mtxlock(&file->_Mutex);
}
}
void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file) {
if (file != nullptr && file->_Mutex != nullptr) {
internal__Mtxunlock(&file->_Mutex);
}
}
OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file,
s32 fd, s32 s_mode, s32 flag) {
if (file == nullptr) {
*Kernel::__Error() = POSIX_ENOMEM;
}
// Preserve mode and index
Libraries::Kernel::PthreadMutexT mtx = file->_Mutex;
Libraries::Kernel::PthreadMutexT* mtx_ptr = &file->_Mutex;
u8 file_index = file->_Idx;
u16 file_mode = file->_Mode & 0x80;
// Real library does a memcpy using a static global FILE object.
// This stored file is just zeros, with the only exception being a handle of -1.
memset(file, 0, sizeof(OrbisFILE));
file->_Handle = -1;
// Not sure what this magic is for, but I'll replicate it.
u8* ptr = &file->_Cbuf;
// Note: this field is supposed to be a pthread mutex.
// Since we don't export pthread HLEs for other functions, I'll avoid handling this for now.
file->_Mutex = nullptr;
file->_Idx = file_index;
file->_Buf = ptr;
file->_Bend = &file->unk2;
file->_Next = ptr;
file->_Rend = ptr;
file->_WRend = ptr;
file->_Wend = ptr;
file->_WWend = ptr;
file->_Rback = ptr;
file->_WRback = &file->unk1;
// Parse inputted mode string
const char* mode_str = mode;
u16 calc_mode = 0;
u16 access_mode = 0;
if (mode_str[0] == 'r') {
calc_mode = 1 | file_mode;
} else if (mode_str[0] == 'w') {
calc_mode = 0x1a | file_mode;
} else if (mode_str[0] == 'a') {
calc_mode = 0x16 | file_mode;
} else {
// Closes the file and returns EINVAL.
file->_Mode = file_mode;
if (flag == 0) {
internal__Mtxinit(mtx_ptr, nullptr);
} else {
file->_Mutex = mtx;
internal__Unlockfilelock(file);
}
internal_fclose(file);
*Kernel::__Error() = POSIX_EINVAL;
return nullptr;
}
file->_Mode = calc_mode;
do {
// This is all basically straight from decomp, need to cleanup at some point.
if (mode_str[1] == '+') {
file_mode = 3;
if ((~calc_mode & 3) == 0) {
break;
}
} else if (mode_str[1] != 'b') {
file_mode = 0x20;
if ((calc_mode & 0x20) != 0) {
break;
}
}
mode_str++;
calc_mode = file_mode | calc_mode;
file->_Mode = calc_mode;
} while (true);
if (path == nullptr && fd >= 0) {
// I guess this is for some internal behavior?
file->_Handle = fd;
} else {
fd = internal__Fopen(path, calc_mode, s_mode == 0x55);
file->_Handle = fd;
}
// Error case
if (fd < 0) {
// Closes the file, but ensures errno is unchanged.
if (flag == 0) {
internal__Mtxinit(mtx_ptr, nullptr);
} else {
file->_Mutex = mtx;
internal__Unlockfilelock(file);
}
s32 old_errno = *Kernel::__Error();
internal_fclose(file);
*Kernel::__Error() = old_errno;
return nullptr;
}
if (flag == 0) {
char mtx_name[0x20];
std::snprintf(mtx_name, 0x20, "FileFD:0x%08X", fd);
internal__Mtxinit(mtx_ptr, mtx_name);
} else {
file->_Mutex = mtx;
}
return file;
}
s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag) {
u32 large_mode = mode;
u16 open_mode = 0600;
if (!flag) {
open_mode = 0666;
}
// Straight from decomp, should probably get cleaned up at some point.
s32 creat_flag = large_mode << 5 & 0x200;
s32 excl_flag = large_mode << 5 & 0x800;
s32 misc_flags = (large_mode & 8) * 0x80 + (large_mode & 4) * 2;
// Real library has an array for this, where large_mode & 3 is used as an index.
// That array has values [0, 0, 1, 2], so this call should match the result.
s32 access_flag = std::max<s32>((large_mode & 3) - 1, 0);
s32 open_flags = creat_flag | misc_flags | excl_flag | access_flag;
return Libraries::Kernel::posix_open(path, open_flags, open_mode);
}
OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode) {
std::scoped_lock lk{g_file_mtx};
LOG_INFO(Lib_LibcInternal, "called, path {}, mode {}", path, mode);
OrbisFILE* file = internal__Fofind();
return internal__Foprep(path, mode, file, -1, 0, 0);
}
s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file) {
if (file == nullptr) {
std::scoped_lock lk{g_file_mtx};
s32 fflush_result = 0;
for (auto& file : g_files) {
s32 res = internal_fflush(file.second);
if (res < 0) {
fflush_result = -1;
}
}
return fflush_result;
}
if ((file->_Mode & 0x2000) != 0) {
internal__Lockfilelock(file);
u16 file_mode = file->_Mode;
u8* file_buf_start = file->_Buf;
u8* file_buf_end = file->_Next;
while (file_buf_start < file_buf_end) {
u64 size_to_write = static_cast<u64>(file_buf_end - file_buf_start);
s32 write_bytes =
Libraries::Kernel::sceKernelWrite(file->_Handle, file_buf_start, size_to_write);
if (write_bytes < 1) {
file_buf_start = file->_Buf;
file->_Next = file_buf_start;
file->_Wend = file_buf_start;
file->_WWend = file_buf_start;
u8* off_mode = reinterpret_cast<u8*>(&file->_Mode) + 1;
*off_mode = *off_mode | 2;
internal__Unlockfilelock(file);
return -1;
}
file_buf_end = file->_Next;
file_buf_start += write_bytes;
}
file->_Next = file_buf_start;
file->_Wend = file_buf_start;
file->_WWend = file_buf_start;
file->_Mode = file_mode & 0xdfff;
internal__Unlockfilelock(file);
}
return 0;
}
s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2) {
if (val1 < val2) {
return val2 - val1;
}
return 0;
}
s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence) {
if ((file->_Mode & 3) == 0) {
return -1;
}
if (internal_fflush(file) != 0) {
return -1;
}
if (whence >= 3) {
*Libraries::Kernel::__Error() = POSIX_EINVAL;
return -1;
}
if (file_pos != nullptr) {
offset = offset + file_pos->_Off;
}
if (whence == 1 && (file->_Mode & 0x1000) != 0) {
s64 val1 = internal__Nnl(file, file->_Rback, &file->_Cbuf);
u8* rsave_ptr = file->_Rsave;
if (rsave_ptr == nullptr) {
rsave_ptr = file->_Rend;
}
s64 val2 = internal__Nnl(file, file->_Next, rsave_ptr);
s64 val3 = internal__Nnl(file, file->_Next, file->_WRend);
offset = offset - (val1 + val2 + val3);
}
s64 result = 0;
if (whence == 2 || (whence == 1 && offset != 0) || (whence == 0 && offset != -1)) {
result = Libraries::Kernel::posix_lseek(file->_Handle, offset, whence);
}
if (result == -1) {
return -1;
}
u16 file_mode = file->_Mode;
if ((file_mode & 0x3000) != 0) {
u8* file_buf = file->_Buf;
file->_Next = file_buf;
file->_Rend = file_buf;
file->_WRend = file_buf;
file->_Wend = file_buf;
file->_WWend = file_buf;
file->_Rback = &file->_Cbuf;
file->_WRback = &file->unk1;
file->_Rsave = nullptr;
}
if (file_pos != nullptr) {
std::memcpy(&file->_Wstate, &file_pos->_Wstate, sizeof(Orbis_Mbstatet));
}
file->_Mode = file_mode & 0xceff;
return 0;
}
s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence) {
internal__Lockfilelock(file);
LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, offset {:#x}, whence {:#x}",
file->_Handle, offset, whence);
s32 result = internal__Fspos(file, nullptr, offset, whence);
internal__Unlockfilelock(file);
return result;
}
s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file) {
if (file->_Rend > file->_Next) {
return 1;
}
if ((file->_Mode & 0x100) != 0) {
return 0;
}
u16 mode = file->_Mode;
if ((mode & 0xa001) != 1) {
// Lot of magic here, might be valuable to figure out what this does.
file->_Mode = (((mode ^ 0x8000) >> 0xf) << 0xe) | mode | 0x200;
return -1;
}
u8* file_buf = file->_Buf;
if ((mode & 0x800) == 0 && file_buf == &file->_Cbuf) {
// Allocate a new file buffer, for now, we'll use host malloc to create it.
// When we have an HLE for malloc, that should be used instead.
u8* new_buffer = std::bit_cast<u8*>(std::malloc(0x10000));
if (new_buffer == nullptr) {
file->_Buf = file_buf;
file->_Bend = file_buf + 1;
} else {
file->_Mode = file->_Mode | 0x40;
file->_Buf = new_buffer;
file->_Bend = new_buffer + 0x10000;
file->_WRend = new_buffer;
file->_WWend = new_buffer;
file_buf = new_buffer;
}
}
file->_Next = file_buf;
file->_Rend = file_buf;
file->_Wend = file_buf;
// Intentional shrinking here, library treats value as 32-bit.
s32 read_result =
Libraries::Kernel::sceKernelRead(file->_Handle, file_buf, file->_Bend - file_buf);
if (read_result < 0) {
u8* off_mode = reinterpret_cast<u8*>(&file->_Mode) + 1;
*off_mode = *off_mode | 0x42;
return -1;
} else if (read_result != 0) {
file->_Mode = file->_Mode | 0x5000;
file->_Rend = file->_Rend + read_result;
return 1;
}
file->_Mode = (file->_Mode & 0xaeff) | 0x4100;
return 0;
}
u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file) {
if (size == 0 || nmemb == 0) {
return 0;
}
internal__Lockfilelock(file);
LOG_TRACE(Lib_LibcInternal, "called, file handle {:#x}, size {:#x}, nmemb {:#x}", file->_Handle,
size, nmemb);
s64 total_size = size * nmemb;
s64 remaining_size = total_size;
if ((file->_Mode & 0x4000) != 0) {
while (remaining_size != 0) {
u8* rback_ptr = file->_Rback;
if (&file->_Cbuf <= rback_ptr) {
break;
}
file->_Rback = rback_ptr + 1;
*ptr = *rback_ptr;
ptr++;
remaining_size--;
}
}
while (remaining_size != 0) {
u8* file_ptr = file->_Rsave;
if (file_ptr == nullptr) {
file_ptr = file->_Rend;
} else {
file->_Rend = file_ptr;
file->_Rsave = nullptr;
}
u8* src = file->_Next;
if (file_ptr <= src) {
s32 res = internal__Frprep(file);
if (res < 1) {
internal__Unlockfilelock(file);
return (total_size - remaining_size) / size;
}
src = file->_Next;
file_ptr = file->_Rend;
}
u64 copy_bytes = std::min<u64>(file_ptr - src, remaining_size);
std::memcpy(ptr, src, copy_bytes);
file->_Next += copy_bytes;
ptr += copy_bytes;
remaining_size -= copy_bytes;
}
internal__Unlockfilelock(file);
return (total_size - remaining_size) / size;
}
void PS4_SYSV_ABI internal__Fofree(OrbisFILE* file) {
u8* cbuf_ptr = &file->_Cbuf;
s8 trunc_mode = static_cast<s8>(file->_Mode);
file->_Mode = 0;
file->_Handle = -1;
file->_Buf = cbuf_ptr;
file->_Next = cbuf_ptr;
file->_Rend = cbuf_ptr;
file->_WRend = cbuf_ptr;
file->_Wend = cbuf_ptr;
file->_WWend = cbuf_ptr;
file->_Rback = cbuf_ptr;
file->_WRback = &file->unk1;
if (trunc_mode < 0) {
// Remove file from vector
g_files.erase(file->_Idx);
internal__Mtxdst(&file->_Mutex);
free(file);
}
}
s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file) {
if (file == nullptr) {
return -1;
}
LOG_INFO(Lib_LibcInternal, "called, file handle {:#x}", file->_Handle);
if ((file->_Mode & 3) == 0 || file->_Handle < 0) {
std::scoped_lock lk{g_file_mtx};
internal__Fofree(file);
*Libraries::Kernel::__Error() = POSIX_EBADF;
} else {
s32 fflush_result = internal_fflush(file);
std::scoped_lock lk{g_file_mtx};
if ((file->_Mode & 0x40) != 0) {
std::free(file->_Buf);
}
file->_Buf = nullptr;
s32 close_result = Libraries::Kernel::posix_close(file->_Handle);
internal__Fofree(file);
// Need to figure out what exactly this means.
return ~-(close_result == 0) | fflush_result;
}
return 0;
}
void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("eLdDw6l0-bU", "libSceLibcInternal", 1, "libSceLibcInternal", internal_snprintf);
LIB_FUNCTION("MUjC4lbHrK4", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fflush);
LIB_FUNCTION("xGT4Mc55ViQ", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofind);
LIB_FUNCTION("dREVnZkAKRE", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Foprep);
LIB_FUNCTION("sQL8D-jio7U", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fopen);
LIB_FUNCTION("A+Y3xfrWLLo", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fspos);
LIB_FUNCTION("Ss3108pBuZY", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Nnl);
LIB_FUNCTION("9s3P+LCvWP8", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Frprep);
LIB_FUNCTION("jVDuvE3s5Bs", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Fofree);
LIB_FUNCTION("vZkmJmvqueY", "libSceLibcInternal", 1, "libSceLibcInternal",
internal__Lockfilelock);
LIB_FUNCTION("0x7rx8TKy2Y", "libSceLibcInternal", 1, "libSceLibcInternal",
internal__Unlockfilelock);
}
void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym) {
// Goal is to be minimally intrusive here to allow LLE for printf/stdout writes.
LIB_FUNCTION("xeYO4u7uyJ0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fopen);
LIB_FUNCTION("rQFVBXp-Cxg", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fseek);
LIB_FUNCTION("lbB+UlZqVG0", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fread);
LIB_FUNCTION("uodLYyUip20", "libSceLibcInternal", 1, "libSceLibcInternal", internal_fclose);
}
} // namespace Libraries::LibcInternal

View File

@ -3,12 +3,96 @@
#pragma once
#include <mutex>
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::LibcInternal {
static std::recursive_mutex g_file_mtx{};
union Orbis__mbstate_t {
u8 __mbstate8[128];
s64 _mbstateL;
};
struct Orbis_Mbstatet {
u64 _Wchar;
u16 _Byte, _State;
s32 : 32;
};
struct Orbisfpos_t {
s64 _Off;
Orbis_Mbstatet _Wstate;
};
struct Orbis__sbuf {
u8* _base;
s32 _size;
};
struct OrbisFILE {
u16 _Mode;
u8 _Idx;
s32 _Handle;
u8 *_Buf, *_Bend, *_Next;
u8 *_Rend, *_Wend, *_Rback;
u16 *_WRback, _WBack[2];
u16 unk1;
u8 *_Rsave, *_WRend, *_WWend;
Orbis_Mbstatet _Wstate;
u8* _Tmpnam;
u8 _Back[6], _Cbuf;
u8 unk2;
Libraries::Kernel::PthreadMutexT _Mutex;
u8* _p;
s32 _r;
s32 _w;
s16 _flags;
s16 _file;
Orbis__sbuf _bf;
s32 _lbfsize;
void* _cookie;
s32 PS4_SYSV_ABI (*_close)(void*);
s32 PS4_SYSV_ABI (*_read)(void*, char*, s32);
Orbisfpos_t PS4_SYSV_ABI (*_seek)(void*, Orbisfpos_t, s32);
s32 (*_write)(void*, const char*, s32);
Orbis__sbuf _ub;
u8* _up;
s32 _ur;
u8 _ubuf[3];
u8 _nbuf[1];
Orbis__sbuf _lb;
s32 _blksize;
Orbisfpos_t _offset;
void* _fl_mutex;
void* _fl_owner;
s32 _fl_count;
s32 _orientation;
Orbis__mbstate_t _mbstate;
};
s32 PS4_SYSV_ABI internal_snprintf(char* s, u64 n, VA_ARGS);
void PS4_SYSV_ABI internal__Lockfilelock(OrbisFILE* file);
void PS4_SYSV_ABI internal__Unlockfilelock(OrbisFILE* file);
OrbisFILE* PS4_SYSV_ABI internal__Fofind();
OrbisFILE* PS4_SYSV_ABI internal__Foprep(const char* path, const char* mode, OrbisFILE* file,
s32 fd, s32 flag1, s32 flag2);
s32 PS4_SYSV_ABI internal__Fopen(const char* path, u16 mode, bool flag);
OrbisFILE* PS4_SYSV_ABI internal_fopen(const char* path, const char* mode);
s64 PS4_SYSV_ABI internal__Nnl(OrbisFILE* file, u8* val1, u8* val2);
s32 PS4_SYSV_ABI internal__Fspos(OrbisFILE* file, Orbisfpos_t* file_pos, s64 offset, s32 whence);
s32 PS4_SYSV_ABI internal_fflush(OrbisFILE* file);
s32 PS4_SYSV_ABI internal_fseek(OrbisFILE* file, s64 offset, s32 whence);
s32 PS4_SYSV_ABI internal__Frprep(OrbisFILE* file);
u64 PS4_SYSV_ABI internal_fread(char* ptr, u64 size, u64 nmemb, OrbisFILE* file);
s32 PS4_SYSV_ABI internal_fclose(OrbisFILE* file);
void RegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym);
void ForceRegisterlibSceLibcInternalIo(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::LibcInternal

View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/kernel/threads.h"
#include "core/libraries/libc_internal/libc_internal_threads.h"
#include "core/libraries/libs.h"
namespace Libraries::LibcInternal {
void getMutexName(char* buf, u64 size, const char* name) {
if (name != nullptr) {
std::snprintf(buf, size, "SceLibcI_%s", name);
} else {
std::snprintf(buf, size, "SceLibcI");
}
}
s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name) {
char mtx_name[0x20];
getMutexName(mtx_name, sizeof(mtx_name), name);
Libraries::Kernel::PthreadMutexAttrT attr{};
s32 result = Libraries::Kernel::posix_pthread_mutexattr_init(&attr);
if (result != 0) {
return 1;
}
result = Libraries::Kernel::posix_pthread_mutexattr_settype(
&attr, Libraries::Kernel::PthreadMutexType::Recursive);
if (result == 0) {
s32 mtx_init_result = Libraries::Kernel::scePthreadMutexInit(mtx, &attr, mtx_name);
result = Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
if (mtx_init_result == 0 && result == 0) {
return 0;
}
} else {
Libraries::Kernel::posix_pthread_mutexattr_destroy(&attr);
}
return 1;
}
s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx) {
s32 result = Libraries::Kernel::posix_pthread_mutex_lock(mtx);
return result != 0;
}
s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx) {
s32 result = Libraries::Kernel::posix_pthread_mutex_unlock(mtx);
return result != 0;
}
s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx) {
s32 result = Libraries::Kernel::posix_pthread_mutex_destroy(mtx);
return result != 0;
}
void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("z7STeF6abuU", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxinit);
LIB_FUNCTION("pE4Ot3CffW0", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxlock);
LIB_FUNCTION("cMwgSSmpE5o", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxunlock);
LIB_FUNCTION("LaPaA6mYA38", "libSceLibcInternal", 1, "libSceLibcInternal", internal__Mtxdst);
}
} // namespace Libraries::LibcInternal

View File

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::LibcInternal {
s32 PS4_SYSV_ABI internal__Mtxinit(Libraries::Kernel::PthreadMutexT* mtx, const char* name);
s32 PS4_SYSV_ABI internal__Mtxlock(Libraries::Kernel::PthreadMutexT* mtx);
s32 PS4_SYSV_ABI internal__Mtxunlock(Libraries::Kernel::PthreadMutexT* mtx);
s32 PS4_SYSV_ABI internal__Mtxdst(Libraries::Kernel::PthreadMutexT* mtx);
void RegisterlibSceLibcInternalThreads(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::LibcInternal

View File

@ -0,0 +1,266 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <png.h>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libpng/pngenc.h"
#include "core/libraries/libs.h"
#include "pngenc_error.h"
namespace Libraries::PngEnc {
struct PngHandler {
png_structp png_ptr;
png_infop info_ptr;
};
struct PngWriter {
u8* cursor;
u8* start;
size_t capacity;
bool cancel_write;
};
static inline int MapPngFilter(u16 filter) {
if (filter == (u16)OrbisPngEncFilterType::All) {
return PNG_ALL_FILTERS;
}
int f = 0;
if (filter & (u16)OrbisPngEncFilterType::None)
f |= PNG_FILTER_NONE;
if (filter & (u16)OrbisPngEncFilterType::Sub)
f |= PNG_FILTER_SUB;
if (filter & (u16)OrbisPngEncFilterType::Up)
f |= PNG_FILTER_UP;
if (filter & (u16)OrbisPngEncFilterType::Average)
f |= PNG_FILTER_AVG;
if (filter & (u16)OrbisPngEncFilterType::Paeth)
f |= PNG_FILTER_PAETH;
return f;
}
void PngWriteFn(png_structp png_ptr, png_bytep data, size_t length) {
PngWriter* ctx = (PngWriter*)png_get_io_ptr(png_ptr);
if ((size_t)(ctx->cursor - ctx->start) + length > ctx->capacity) {
LOG_ERROR(Lib_Png, "PNG output buffer too small");
ctx->cancel_write = true;
return;
}
memcpy(ctx->cursor, data, length);
ctx->cursor += length;
}
void PngFlushFn(png_structp png_ptr) {}
void PngEncError(png_structp png_ptr, png_const_charp error_message) {
LOG_ERROR(Lib_Png, "PNG error {}", error_message);
}
void PngEncWarning(png_structp png_ptr, png_const_charp error_message) {
LOG_ERROR(Lib_Png, "PNG warning {}", error_message);
}
s32 PS4_SYSV_ABI scePngEncCreate(const OrbisPngEncCreateParam* param, void* memoryAddress,
u32 memorySize, OrbisPngEncHandle* handle) {
if (param == nullptr || param->attribute != 0) {
LOG_ERROR(Lib_Png, "Invalid param");
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
}
if (memoryAddress == nullptr) {
LOG_ERROR(Lib_Png, "Invalid memory address");
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
}
if (param->max_image_width - 1 > 1000000) {
LOG_ERROR(Lib_Png, "Invalid Size, width = {}", param->max_image_width);
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
}
auto pngh = (PngHandler*)memoryAddress;
pngh->png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PngEncError, PngEncWarning);
if (pngh->png_ptr == nullptr)
return ORBIS_PNG_ENC_ERROR_FATAL;
pngh->info_ptr = png_create_info_struct(pngh->png_ptr);
if (pngh->info_ptr == nullptr) {
png_destroy_write_struct(&pngh->png_ptr, nullptr);
return false;
}
*handle = pngh;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI scePngEncDelete(OrbisPngEncHandle handle) {
auto pngh = (PngHandler*)handle;
png_destroy_write_struct(&pngh->png_ptr, &pngh->info_ptr);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI scePngEncEncode(OrbisPngEncHandle handle, const OrbisPngEncEncodeParam* param,
OrbisPngEncOutputInfo* outputInfo) {
LOG_TRACE(Lib_Png, "called png addr = {}, image addr = {}, image size = {}",
(void*)param->png_mem_addr, (void*)param->image_mem_addr, param->image_mem_size);
if (handle == nullptr) {
LOG_ERROR(Lib_Png, "Invalid handle");
return ORBIS_PNG_ENC_ERROR_INVALID_HANDLE;
}
if (param == nullptr) {
LOG_ERROR(Lib_Png, "Invalid param");
return ORBIS_PNG_ENC_ERROR_INVALID_PARAM;
}
if (param->image_mem_addr == nullptr || param->png_mem_addr == nullptr) {
LOG_ERROR(Lib_Png, "Invalid input or output address");
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
}
if (param->png_mem_size == 0 || param->image_mem_size == 0 || param->image_height == 0 ||
param->image_width == 0) {
LOG_ERROR(Lib_Png, "Invalid Size");
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
}
auto pngh = (PngHandler*)handle;
if (setjmp(png_jmpbuf(pngh->png_ptr))) {
LOG_ERROR(Lib_Png, "LibPNG aborted encode");
return ORBIS_PNG_ENC_ERROR_FATAL;
}
int png_color_type = PNG_COLOR_TYPE_RGB;
if (param->color_space == OrbisPngEncColorSpace::RGBA) {
png_color_type |= PNG_COLOR_MASK_ALPHA;
}
int png_interlace_type = PNG_INTERLACE_NONE;
int png_compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
int png_filter_method = PNG_FILTER_TYPE_DEFAULT;
PngWriter writer{};
writer.cursor = param->png_mem_addr;
writer.start = param->png_mem_addr;
writer.capacity = param->png_mem_size;
png_set_write_fn(pngh->png_ptr, &writer, PngWriteFn, PngFlushFn);
png_set_IHDR(pngh->png_ptr, pngh->info_ptr, param->image_width, param->image_height,
param->bit_depth, png_color_type, png_interlace_type, png_compression_type,
png_filter_method);
if (param->pixel_format == OrbisPngEncPixelFormat::B8G8R8A8) {
png_set_bgr(pngh->png_ptr);
}
png_set_compression_level(pngh->png_ptr, std::clamp<u16>(param->compression_level, 0, 9));
png_set_filter(pngh->png_ptr, 0, MapPngFilter(param->filter_type));
png_write_info(pngh->png_ptr, pngh->info_ptr);
int channels = 4;
size_t row_stride = param->image_width * channels;
uint32_t processed_height = 0;
if (param->color_space == OrbisPngEncColorSpace::RGBA) {
for (; processed_height < param->image_height; ++processed_height) {
png_bytep row = (png_bytep)param->image_mem_addr + processed_height * row_stride;
png_write_row(pngh->png_ptr, row);
if (outputInfo != nullptr) {
outputInfo->processed_height = processed_height;
}
if (writer.cancel_write) {
LOG_ERROR(Lib_Png, "Ran out of room to write PNG");
return ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW;
}
}
} else {
// our input data is always rgba but when outputting without an alpha channel, libpng
// expects the input to not have alpha either, i couldn't find a way around this easily?
// png_strip_alpha is for reading and set_background wasn't working, this seems fine...?
std::vector<uint8_t> rgb_row(param->image_width * 3);
for (; processed_height < param->image_height; ++processed_height) {
const unsigned char* src =
param->image_mem_addr + processed_height * param->image_pitch;
uint8_t* dst = rgb_row.data();
for (uint32_t x = 0; x < param->image_width; ++x) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
src += 4; // skip reading alpha channel
dst += 3;
}
png_write_row(pngh->png_ptr, rgb_row.data());
if (outputInfo != nullptr) {
outputInfo->processed_height = processed_height;
}
if (writer.cancel_write) {
LOG_ERROR(Lib_Png, "Ran out of room to write PNG");
return ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW;
}
}
}
png_write_flush(pngh->png_ptr);
png_write_end(pngh->png_ptr, pngh->info_ptr);
if (outputInfo != nullptr) {
outputInfo->data_size = writer.cursor - writer.start;
outputInfo->processed_height = processed_height;
}
return writer.cursor - writer.start;
}
s32 PS4_SYSV_ABI scePngEncQueryMemorySize(const OrbisPngEncCreateParam* param) {
if (param == nullptr) {
LOG_ERROR(Lib_Png, "Invalid Address");
return ORBIS_PNG_ENC_ERROR_INVALID_ADDR;
}
if (param->attribute != 0 || param->max_filter_number > 5) {
LOG_ERROR(Lib_Png, "Invalid Param, attribute = {}, max_filter_number = {}",
param->attribute, param->max_filter_number);
return ORBIS_PNG_ENC_ERROR_INVALID_PARAM;
}
if (param->max_image_width - 1 > 1000000) {
LOG_ERROR(Lib_Png, "Invalid Size, width = {}", param->max_image_width);
return ORBIS_PNG_ENC_ERROR_INVALID_SIZE;
}
return sizeof(PngHandler);
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("7aGTPfrqT9s", "libScePngEnc", 1, "libScePngEnc", scePngEncCreate);
LIB_FUNCTION("RUrWdwTWZy8", "libScePngEnc", 1, "libScePngEnc", scePngEncDelete);
LIB_FUNCTION("xgDjJKpcyHo", "libScePngEnc", 1, "libScePngEnc", scePngEncEncode);
LIB_FUNCTION("9030RnBDoh4", "libScePngEnc", 1, "libScePngEnc", scePngEncQueryMemorySize);
};
} // namespace Libraries::PngEnc

View File

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::PngEnc {
enum class OrbisPngEncAttribute { None = 0 };
enum class OrbisPngEncColorSpace : u16 { RGB = 3, RGBA = 19 };
enum class OrbisPngEncPixelFormat : u16 { R8G8B8A8 = 0, B8G8R8A8 };
enum class OrbisPngEncFilterType : u16 {
None = 0,
Sub = 1,
Up = 2,
Average = 4,
Paeth = 8,
All = 15
};
struct OrbisPngEncCreateParam {
u32 this_size;
u32 attribute;
u32 max_image_width;
u32 max_filter_number;
};
struct OrbisPngEncEncodeParam {
const u8* image_mem_addr;
u8* png_mem_addr;
u32 image_mem_size;
u32 png_mem_size;
u32 image_width;
u32 image_height;
u32 image_pitch;
OrbisPngEncPixelFormat pixel_format;
OrbisPngEncColorSpace color_space;
u16 bit_depth;
u16 clut_number;
u16 filter_type;
u16 compression_level;
};
struct OrbisPngEncOutputInfo {
u32 data_size;
u32 processed_height;
};
using OrbisPngEncHandle = void*;
s32 PS4_SYSV_ABI scePngEncCreate(const OrbisPngEncCreateParam* param, void* memoryAddress,
u32 memorySize, OrbisPngEncHandle* handle);
s32 PS4_SYSV_ABI scePngEncDelete(OrbisPngEncHandle handle);
s32 PS4_SYSV_ABI scePngEncEncode(OrbisPngEncHandle, const OrbisPngEncEncodeParam* param,
OrbisPngEncOutputInfo* outputInfo);
s32 PS4_SYSV_ABI scePngEncQueryMemorySize(const OrbisPngEncCreateParam* param);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::PngEnc

View File

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
// PngEnc library
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_ADDR = 0x80690101;
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_SIZE = 0x80690102;
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_PARAM = 0x80690103;
constexpr int ORBIS_PNG_ENC_ERROR_INVALID_HANDLE = 0x80690104;
constexpr int ORBIS_PNG_ENC_ERROR_DATA_OVERFLOW = 0x80690110;
constexpr int ORBIS_PNG_ENC_ERROR_FATAL = 0x80690120;

View File

@ -35,11 +35,14 @@
#include "core/libraries/np/np_commerce.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_party.h"
#include "core/libraries/np/np_profile_dialog.h"
#include "core/libraries/np/np_score.h"
#include "core/libraries/np/np_sns_facebook_dialog.h"
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/np/np_tus.h"
#include "core/libraries/np/np_web_api.h"
#include "core/libraries/np/np_web_api2.h"
#include "core/libraries/pad/pad.h"
@ -77,6 +80,7 @@ namespace Libraries {
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
LOG_INFO(Lib_Kernel, "Initializing HLE libraries");
Libraries::Kernel::RegisterLib(sym);
Libraries::LibcInternal::ForceRegisterLib(sym);
Libraries::GnmDriver::RegisterLib(sym);
Libraries::VideoOut::RegisterLib(sym);
Libraries::UserService::RegisterLib(sym);
@ -97,6 +101,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Np::NpCommerce::RegisterLib(sym);
Libraries::Np::NpCommon::RegisterLib(sym);
Libraries::Np::NpManager::RegisterLib(sym);
Libraries::Np::NpMatching2::RegisterLib(sym);
Libraries::Np::NpScore::RegisterLib(sym);
Libraries::Np::NpTrophy::RegisterLib(sym);
Libraries::Np::NpWebApi::RegisterLib(sym);
@ -105,6 +110,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym);
Libraries::Np::NpAuth::RegisterLib(sym);
Libraries::Np::NpParty::RegisterLib(sym);
Libraries::Np::NpPartner::RegisterLib(sym);
Libraries::Np::NpTus::RegisterLib(sym);
Libraries::ScreenShot::RegisterLib(sym);
Libraries::AppContent::RegisterLib(sym);
Libraries::PngDec::RegisterLib(sym);

View File

@ -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;
}

View File

@ -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();

View File

@ -803,6 +803,7 @@ int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid) {
LOG_DEBUG(Lib_Net, "called, epollid = {} ({})", epollid, file->epoll->name);
file->epoll->Destroy();
FDTable::Instance()->DeleteHandle(epollid);
return ORBIS_OK;
}

View File

@ -335,6 +335,7 @@ int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Close();
if (returncode >= 0) {
FDTable::Instance()->DeleteHandle(s);
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());

View File

@ -124,7 +124,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;
}

View File

@ -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

View File

@ -13,4 +13,12 @@ constexpr int ORBIS_NP_ERROR_INVALID_SIZE = 0x80550011;
constexpr int ORBIS_NP_ERROR_ABORTED = 0x80550012;
constexpr int ORBIS_NP_ERROR_REQUEST_MAX = 0x80550013;
constexpr int ORBIS_NP_ERROR_REQUEST_NOT_FOUND = 0x80550014;
constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015;
constexpr int ORBIS_NP_ERROR_INVALID_ID = 0x80550015;
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = 0x80550704;
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = 0x80550706;
constexpr int ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = 0x8055070c;
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ID = 0x8055070e;
constexpr int ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = 0x80550714;
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = 0x80550718;
constexpr int ORBIS_NP_COMMUNITY_ERROR_TOO_MANY_NPID = 0x80550719;

View File

@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <mutex>
#include <variant>
#include "common/config.h"
#include "common/logging/log.h"
@ -18,6 +20,9 @@ static bool g_signed_in = false;
static s32 g_active_requests = 0;
static std::mutex g_request_mutex;
static std::map<std::string, std::function<void()>> g_np_callbacks;
static std::mutex g_np_callbacks_mutex;
// Internal types for storing request-related information
enum class NpRequestState {
None = 0,
@ -667,6 +672,19 @@ s32 PS4_SYSV_ABI sceNpGetState(Libraries::UserService::OrbisUserServiceUserId us
return ORBIS_OK;
}
s32 PS4_SYSV_ABI
sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServiceUserId* user_id) {
if (user_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
if (!g_signed_in) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
*user_id = 1;
LOG_DEBUG(Lib_NpManager, "userid({}) = {}", account_id, *user_id);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId user_id,
bool* has_signed_up) {
LOG_DEBUG(Lib_NpManager, "called");
@ -684,8 +702,22 @@ struct NpStateCallbackForNpToolkit {
NpStateCallbackForNpToolkit NpStateCbForNp;
struct NpStateCallback {
std::variant<OrbisNpStateCallback, OrbisNpStateCallbackA> func;
void* userdata;
};
NpStateCallback NpStateCb;
s32 PS4_SYSV_ABI sceNpCheckCallback() {
LOG_DEBUG(Lib_NpManager, "(STUBBED) called");
std::scoped_lock lk{g_np_callbacks_mutex};
for (auto i : g_np_callbacks) {
(i.second)();
}
return ORBIS_OK;
}
@ -694,6 +726,40 @@ s32 PS4_SYSV_ABI sceNpCheckCallbackForLib() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpRegisterStateCallback(OrbisNpStateCallback callback, void* userdata) {
static s32 id = 0;
LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata);
NpStateCb.func = callback;
NpStateCb.userdata = userdata;
return id;
}
s32 PS4_SYSV_ABI sceNpRegisterStateCallbackA(OrbisNpStateCallbackA callback, void* userdata) {
static s32 id = 0;
LOG_ERROR(Lib_NpManager, "(STUBBED) called, userdata = {}", userdata);
NpStateCb.func = callback;
NpStateCb.userdata = userdata;
return id;
}
struct NpReachabilityStateCallback {
OrbisNpReachabilityStateCallback func;
void* userdata;
};
NpReachabilityStateCallback NpReachabilityCb;
s32 PS4_SYSV_ABI sceNpRegisterNpReachabilityStateCallback(OrbisNpReachabilityStateCallback callback,
void* userdata) {
static s32 id = 0;
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
NpReachabilityCb.func = callback;
NpReachabilityCb.userdata = userdata;
return id;
}
s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback,
void* userdata) {
static s32 id = 0;
@ -703,6 +769,22 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT
return id;
}
void RegisterNpCallback(std::string key, std::function<void()> cb) {
std::scoped_lock lk{g_np_callbacks_mutex};
LOG_DEBUG(Lib_NpManager, "registering callback processing for {}", key);
g_np_callbacks.emplace(key, cb);
}
void DeregisterNpCallback(std::string key) {
std::scoped_lock lk{g_np_callbacks_mutex};
LOG_DEBUG(Lib_NpManager, "deregistering callback processing for {}", key);
g_np_callbacks.erase(key);
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
g_signed_in = EmulatorSettings::GetInstance()->IsPSNSignedIn();
@ -741,9 +823,14 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("p-o74CnoNzY", "libSceNpManager", 1, "libSceNpManager", sceNpGetNpId);
LIB_FUNCTION("XDncXQIJUSk", "libSceNpManager", 1, "libSceNpManager", sceNpGetOnlineId);
LIB_FUNCTION("eQH7nWPcAgc", "libSceNpManager", 1, "libSceNpManager", sceNpGetState);
LIB_FUNCTION("VgYczPGB5ss", "libSceNpManager", 1, "libSceNpManager", sceNpGetUserIdByAccountId);
LIB_FUNCTION("Oad3rvY-NJQ", "libSceNpManager", 1, "libSceNpManager", sceNpHasSignedUp);
LIB_FUNCTION("3Zl8BePTh9Y", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallback);
LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib);
LIB_FUNCTION("VfRSmPmj8Q8", "libSceNpManager", 1, "libSceNpManager",
sceNpRegisterStateCallback);
LIB_FUNCTION("hw5KNqAAels", "libSceNpManager", 1, "libSceNpManager",
sceNpRegisterNpReachabilityStateCallback);
LIB_FUNCTION("JELHf4xPufo", "libSceNpManagerForToolkit", 1, "libSceNpManager",
sceNpCheckCallbackForLib);
LIB_FUNCTION("0c7HbXRKUt4", "libSceNpManagerForToolkit", 1, "libSceNpManager",

View File

@ -3,6 +3,8 @@
#pragma once
#include <functional>
#include "common/types.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_types.h"
@ -23,20 +25,28 @@ enum class OrbisNpState : u32 {
SignedIn = 2,
};
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
enum class OrbisNpGamePresenseStatus {
Offline = 0,
Online = 1,
};
enum class OrbisNpReachabilityState {
Unavailable = 0,
Available = 1,
Reachable = 2,
};
using OrbisNpStateCallback =
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state,
OrbisNpId* npId, void* userdata);
using OrbisNpStateCallbackA = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
using OrbisNpReachabilityStateCallback =
PS4_SYSV_ABI void (*)(Libraries::UserService::OrbisUserServiceUserId userId,
OrbisNpReachabilityState state, void* userdata);
enum class OrbisNpGamePresenseStatus {
Offline = 0,
Online = 1,
};
struct OrbisNpCountryCode {
char country_code[2];
char end;
@ -80,5 +90,13 @@ struct OrbisNpCreateAsyncRequestParameter {
u8 padding[4];
};
void RegisterNpCallback(std::string key, std::function<void()> cb);
void DeregisterNpCallback(std::string key);
s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpId* np_id);
s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpOnlineId* online_id);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpManager

View File

@ -0,0 +1,812 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <deque>
#include <mutex>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpMatching2 {
static bool g_initialized = false;
static OrbisNpMatching2ContextId contextId = 1;
struct NpMatching2ContextEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2Event event;
OrbisNpMatching2EventCause cause;
int errorCode;
};
struct NpMatching2LobbyEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2LobbyId lobbyId;
OrbisNpMatching2Event event;
void* data;
};
struct NpMatching2RoomEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2Event event;
void* data;
};
static std::mutex g_events_mutex;
static std::deque<NpMatching2ContextEvent> g_ctx_events;
static std::deque<NpMatching2LobbyEvent> g_lobby_events;
static std::deque<NpMatching2RoomEvent> g_room_events;
static std::mutex g_responses_mutex;
static std::deque<std::function<void()>> g_responses;
struct OrbisNpMatching2CreateContextParameter {
Libraries::Np::OrbisNpId* npId;
void* npCommunicationId;
void* npPassphrase;
Libraries::Np::OrbisNpServiceLabel serviceLabel;
u64 size;
};
static_assert(sizeof(OrbisNpMatching2CreateContextParameter) == 0x28);
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
OrbisNpMatching2ContextId* ctxId) {
LOG_DEBUG(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}",
param->npId->handle.data, param->serviceLabel, param->size);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!param || param->size != 0x28 || !ctxId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
*ctxId = contextId++;
return ORBIS_OK;
}
struct OrbisNpMatching2CreateContextParameterA {
Libraries::UserService::OrbisUserServiceUserId userId;
Libraries::Np::OrbisNpServiceLabel serviceLabel;
u64 size;
};
static_assert(sizeof(OrbisNpMatching2CreateContextParameterA) == 16);
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
OrbisNpMatching2ContextId* ctxId) {
LOG_DEBUG(Lib_NpMatching2, "called, userId = {}, serviceLabel = {}, size = {}", param->userId,
param->serviceLabel, param->size);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!param || param->size != 0x10 || !ctxId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
*ctxId = contextId++;
return ORBIS_OK;
}
using OrbisNpMatching2RequestCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId,
OrbisNpMatching2RequestId,
OrbisNpMatching2Event, int, void*,
void*);
struct OrbisNpMatching2RequestOptParam {
OrbisNpMatching2RequestCallback callback;
void* arg;
u32 timeout;
u16 appId;
u8 dummy[2];
};
static std::optional<OrbisNpMatching2RequestOptParam> defaultRequestOptParam = std::nullopt;
auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) {
return requestOpt ? *requestOpt
: (defaultRequestOptParam ? defaultRequestOptParam
: std::optional<OrbisNpMatching2RequestOptParam>{});
}
struct OrbisNpMatching2CreateJoinRoomRequestA {
u16 maxSlot;
OrbisNpMatching2TeamId teamId;
u8 pad[5];
OrbisNpMatching2Flags flags;
OrbisNpMatching2WorldId worldId;
OrbisNpMatching2LobbyId lobbyId;
void* roomPasswd;
void* passwdSlotMask;
void* groupConfig;
u64 groupConfigs;
void* joinGroupLabel;
Libraries::Np::OrbisNpAccountId* allowedUser;
u64 allowedUsers;
Libraries::Np::OrbisNpAccountId* blockedUser;
u64 blockedUsers;
void* internalBinAttr;
u64 internalBinAttrs;
void* externalSearchIntAttr;
u64 externalSearchIntAttrs;
void* externalSearchBinAttr;
u64 externalSearchBinAttrs;
void* externalBinAttr;
u64 externalBinAttrs;
void* memberInternalBinAttr;
u64 memberInternalBinAttrs;
void* signalingParam;
};
static_assert(sizeof(OrbisNpMatching2CreateJoinRoomRequestA) == 184);
struct OrbisNpMatching2RoomDataInternal {
u16 publicSlots;
u16 privateSlots;
u16 openPublicSlots;
u16 openPrivateSlots;
u16 maxSlot;
OrbisNpMatching2ServerId serverId;
OrbisNpMatching2WorldId worldId;
OrbisNpMatching2LobbyId lobbyId;
OrbisNpMatching2RoomId roomId;
u64 passwdSlotMask;
u64 joinedSlotMask;
void* roomGroup;
u64 roomGroups;
OrbisNpMatching2Flags flags;
u8 pad[4];
void* internalBinAttr;
u64 internalBinAttrs;
};
struct OrbisNpMatching2RoomMemberDataInternalA {
OrbisNpMatching2RoomMemberDataInternalA* next;
u64 joinDateTicks;
Libraries::Np::OrbisNpPeerAddressA user;
Libraries::Np::OrbisNpOnlineId onlineId;
u8 pad[4];
OrbisNpMatching2RoomMemberId memberId;
OrbisNpMatching2TeamId teamId;
OrbisNpMatching2NatType natType;
OrbisNpMatching2Flags flags;
void* roomGroup;
void* roomMemberInternalBinAttr;
u64 roomMemberInternalBinAttrs;
};
struct OrbisNpMatching2RoomMemberDataInternalListA {
OrbisNpMatching2RoomMemberDataInternalA* members;
u64 membersNum;
OrbisNpMatching2RoomMemberDataInternalA* me;
OrbisNpMatching2RoomMemberDataInternalA* owner;
};
struct OrbisNpMatching2CreateJoinRoomResponseA {
OrbisNpMatching2RoomDataInternal* roomData;
OrbisNpMatching2RoomMemberDataInternalListA members;
};
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2CreateJoinRoomRequestA* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
LOG_DEBUG(Lib_NpMatching2,
"maxSlot = {}, teamId = {}, worldId = {}, lobbyId = {}, groupConfig = {}, "
"joinGroupLabel = {}",
request->maxSlot, request->teamId, request->worldId, request->lobbyId,
request->groupConfig, request->joinGroupLabel);
static OrbisNpMatching2RequestId id = 10;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
auto requestCopy = *request;
g_responses.emplace_back([=]() {
Libraries::Np::OrbisNpOnlineId onlineId{};
if (NpManager::sceNpGetOnlineId(1, &onlineId) != ORBIS_OK) {
return;
}
OrbisNpMatching2RoomMemberDataInternalA me{
nullptr,
0,
{0xace104e, Libraries::Np::OrbisNpPlatformType::PS4},
onlineId,
{0, 0, 0, 0},
1,
requestCopy.teamId,
1,
0,
nullptr,
nullptr,
0};
OrbisNpMatching2RoomDataInternal room{requestCopy.maxSlot,
0,
static_cast<u16>(requestCopy.maxSlot - 1u),
0,
15,
0xac,
requestCopy.worldId,
requestCopy.lobbyId,
0x10,
0,
0,
nullptr,
0,
0,
{0, 0, 0, 0},
nullptr,
0};
OrbisNpMatching2CreateJoinRoomResponseA resp{&room, {&me, 1, &me, &me}};
optParam->callback(ctxId, reqIdCopy,
ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A, 0, &resp,
optParam->arg);
});
}
return ORBIS_OK;
}
using OrbisNpMatching2ContextCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId,
OrbisNpMatching2Event event,
OrbisNpMatching2EventCause cause,
int errorCode, void* userdata);
std::function<void(const NpMatching2ContextEvent*)> npMatching2ContextCallback = nullptr;
int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback,
void* userdata) {
LOG_DEBUG(Lib_NpMatching2, "called, userdata = {}", userdata);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
npMatching2ContextCallback = [callback, userdata](auto arg) {
callback(arg->contextId, arg->event, arg->cause, arg->errorCode, userdata);
};
return ORBIS_OK;
}
using OrbisNpMatching2LobbyEventCallback =
PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2LobbyId lobbyId,
OrbisNpMatching2Event event, void* data, void* userdata);
std::function<void(const NpMatching2LobbyEvent*)> npMatching2LobbyCallback = nullptr;
int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
npMatching2LobbyCallback = [callback, userdata](auto arg) {
callback(arg->contextId, arg->lobbyId, arg->event, arg->data, userdata);
};
return ORBIS_OK;
}
using OrbisNpMatching2RoomEventCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId,
OrbisNpMatching2RoomId roomId,
OrbisNpMatching2Event event,
void* data, void* userdata);
std::function<void(const NpMatching2RoomEvent*)> npMatching2RoomCallback = nullptr;
int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomEventCallback callback,
void* userdata) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
npMatching2RoomCallback = [callback, userdata](auto arg) {
callback(arg->contextId, arg->roomId, arg->event, arg->data, userdata);
};
return ORBIS_OK;
}
struct OrbisNpMatching2SignalingEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2RoomMemberId roomMemberId;
OrbisNpMatching2Event event;
int errorCode;
};
using OrbisNpMatching2SignalingCallback =
PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId roomMemberId, OrbisNpMatching2Event event,
int errorCode, void* userdata);
std::function<void(const OrbisNpMatching2SignalingEvent*)> npMatching2SignalingCallback = nullptr;
int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SignalingCallback callback,
void* userdata) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
npMatching2SignalingCallback = [callback, userdata](auto arg) {
callback(arg->contextId, arg->roomId, arg->roomMemberId, arg->event, arg->errorCode,
userdata);
};
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, timeout = {}", ctxId, timeout);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
std::scoped_lock lk{g_events_mutex};
if (Config::getIsConnectedToNetwork() && Config::getPSNSignedIn()) {
g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED,
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, 0);
} else {
// error confirmed with a real console disconnected from the internet
constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2;
g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER,
ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR,
ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT);
}
return ORBIS_OK;
}
void ProcessEvents() {
{
std::scoped_lock lk{g_events_mutex};
if (npMatching2ContextCallback) {
while (!g_ctx_events.empty()) {
npMatching2ContextCallback(&g_ctx_events.front());
g_ctx_events.pop_front();
}
}
if (npMatching2LobbyCallback) {
while (!g_lobby_events.empty()) {
npMatching2LobbyCallback(&g_lobby_events.front());
g_lobby_events.pop_front();
}
}
if (npMatching2RoomCallback) {
while (!g_room_events.empty()) {
npMatching2RoomCallback(&g_room_events.front());
g_room_events.pop_front();
}
}
}
std::scoped_lock lk{g_responses_mutex};
while (!g_responses.empty()) {
(g_responses.front())();
g_responses.pop_front();
}
}
struct OrbisNpMatching2InitializeParameter {
u64 poolSize;
//
};
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param) {
LOG_DEBUG(Lib_NpMatching2, "called");
if (g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED;
}
g_initialized = true;
Libraries::Np::NpManager::RegisterNpCallback("NpMatching2", ProcessEvents);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2Terminate() {
LOG_DEBUG(Lib_NpMatching2, "called");
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
g_initialized = false;
Libraries::Np::NpManager::DeregisterNpCallback("NpMatching2");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!requestOpt) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
defaultRequestOptParam = *requestOpt;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2ServerId* serverId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!serverId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
*serverId = 0xac;
return ORBIS_OK;
}
struct OrbisNpMatching2GetWorldInfoListRequest {
OrbisNpMatching2ServerId serverId;
};
struct OrbisNpMatching2World {
OrbisNpMatching2World* next;
OrbisNpMatching2WorldId worldId;
u32 lobbiesNum;
u32 maxLobbyMembersNum;
u32 lobbyMembersNum;
u32 roomsNum;
u32 roomMembersNum;
u8 pad[3];
};
struct OrbisNpMatching2GetWorldInfoListResponse {
OrbisNpMatching2World* world;
u64 worldNum;
};
int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetWorldInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, request.serverId = {}, requestOpt = {}", ctxId,
request ? request->serverId : 0xFFFF, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 1;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
auto reqIdCopy = *requestId;
std::scoped_lock lk{g_responses_mutex};
g_responses.emplace_back([=]() {
OrbisNpMatching2World w{nullptr, 1, 10, 0, 10, 0, {}};
OrbisNpMatching2GetWorldInfoListResponse resp{&w, 1};
optParam->callback(ctxId, reqIdCopy,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, 0, &resp,
optParam->arg);
});
}
return ORBIS_OK;
}
struct OrbisNpMatching2PresenceOptionData {
u8 data[16];
u64 len;
};
struct OrbisNpMatching2LeaveRoomRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2PresenceOptionData optData;
};
int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2LeaveRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 500;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
g_responses.emplace_back([=]() {
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM, 0,
nullptr, optParam->arg);
});
}
return ORBIS_OK;
}
struct OrbisNpMatching2RangeFilter {
u32 start;
u32 max;
};
struct OrbisNpMatching2SearchRoomRequest {
int option;
OrbisNpMatching2WorldId worldId;
OrbisNpMatching2LobbyId lobbyId;
OrbisNpMatching2RangeFilter rangeFilter;
OrbisNpMatching2Flags flags1;
OrbisNpMatching2Flags flags2;
void* intFilter;
u64 intFilters;
void* binFilter;
u64 binFilters;
OrbisNpMatching2AttributeId* attr;
u64 attrs;
};
struct OrbisNpMatching2Range {
u32 start;
u32 total;
u32 results;
u8 pad[4];
};
struct OrbisNpMatching2SearchRoomResponseA {
OrbisNpMatching2Range range;
void* roomDataExt;
};
int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SearchRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 1;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
auto requestCopy = *request;
g_responses.emplace_back([=]() {
OrbisNpMatching2SearchRoomResponseA resp{{0, 0, 0, {}}, nullptr};
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A, 0,
&resp, optParam->arg);
});
}
return ORBIS_OK;
}
struct OrbisNpMatching2SetUserInfoRequest {
OrbisNpMatching2ServerId serverId;
u8 padding[6];
void* userBinAttr;
u64 userBinAttrs;
};
int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SetUserInfoRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 100;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
g_responses.emplace_back([=]() {
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO, 0,
nullptr, optParam->arg);
});
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 1000;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
g_responses.emplace_back([=]() {
optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE,
0, nullptr, optParam->arg);
});
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(OrbisNpMatching2ContextId ctxId, void* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 800;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
g_responses.emplace_back([=]() {
optParam->callback(ctxId, reqIdCopy,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL, 0, nullptr,
optParam->arg);
});
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(OrbisNpMatching2ContextId ctxId, void* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
static OrbisNpMatching2RequestId id = 200;
*requestId = id++;
if (auto optParam = GetOptParam(requestOpt); optParam) {
LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout,
optParam->appId);
std::scoped_lock lk{g_responses_mutex};
auto reqIdCopy = *requestId;
g_responses.emplace_back([=]() {
optParam->callback(ctxId, reqIdCopy,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL, 0, nullptr,
optParam->arg);
});
}
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("10t3e5+JPnU", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2Initialize);
LIB_FUNCTION("Mqp3lJ+sjy4", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2Terminate);
LIB_FUNCTION("YfmpW719rMo", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2CreateContext);
LIB_FUNCTION("ajvzc8e2upo", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2CreateContextA);
LIB_FUNCTION("V6KSpKv9XJE", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2CreateJoinRoomA);
LIB_FUNCTION("fQQfP87I7hs", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2RegisterContextCallback);
LIB_FUNCTION("4Nj7u5B5yCA", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2RegisterLobbyEventCallback);
LIB_FUNCTION("p+2EnxmaAMM", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2RegisterRoomEventCallback);
LIB_FUNCTION("0UMeWRGnZKA", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2RegisterSignalingCallback);
LIB_FUNCTION("7vjNQ6Z1op0", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2ContextStart);
LIB_FUNCTION("LhCPctIICxQ", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2GetServerId);
LIB_FUNCTION("rJNPJqDCpiI", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2GetWorldInfoList);
LIB_FUNCTION("BD6kfx442Do", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2LeaveRoom);
LIB_FUNCTION("+8e7wXLmjds", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SetDefaultRequestOptParam);
LIB_FUNCTION("VqZX7POg2Mk", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SearchRoom);
LIB_FUNCTION("Iw2h0Jrrb5U", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SendRoomMessage);
LIB_FUNCTION("meEjIdbjAA0", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SetUserInfo);
LIB_FUNCTION("q7GK98-nYSE", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SetRoomDataExternal);
LIB_FUNCTION("S9D8JSYIrjE", "libSceNpMatching2", 1, "libSceNpMatching2",
sceNpMatching2SetRoomDataInternal);
};
} // namespace Libraries::Np::NpMatching2

View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpMatching2 {
using OrbisNpMatching2AttributeId = u16;
using OrbisNpMatching2ContextId = u16;
using OrbisNpMatching2Event = u16;
using OrbisNpMatching2EventCause = u8;
using OrbisNpMatching2Flags = u32;
using OrbisNpMatching2LobbyId = u64;
using OrbisNpMatching2NatType = u8;
using OrbisNpMatching2RequestId = u16;
using OrbisNpMatching2RoomId = u64;
using OrbisNpMatching2RoomMemberId = u16;
using OrbisNpMatching2ServerId = u16;
using OrbisNpMatching2TeamId = u8;
using OrbisNpMatching2WorldId = u32;
constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED = 0x80550c01;
constexpr int ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = 0x80550c02;
constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT = 0x80550c15;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM = 0x0101;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A = 0x7101;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST = 0x0002;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM = 0x0103;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL = 0x0004;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x1106;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO = 0x0007;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER = 0x6F01;
constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED = 0x6F02;
constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR = 10;
constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION = 11;
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpMatching2

View File

@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_partner_error.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpPartner {
static bool g_library_init = false;
std::mutex g_library_mutex{};
/**
* Terminates the library
*/
s32 PS4_SYSV_ABI Func_A4CC5784DA33517F() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
std::scoped_lock lk{g_library_mutex};
g_library_init = false;
return ORBIS_OK;
}
/**
* Aborts requests started by Func_F8E9DB52CD425743
*/
s32 PS4_SYSV_ABI Func_A507D84D91F39CC7() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
// Request logic is unimplemented, so this does nothing.
return ORBIS_OK;
}
/**
* Initializes the library
*/
s32 PS4_SYSV_ABI Func_EC2C48E74FF19429() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
g_library_init = true;
// Also retrieves and sends compiled SDK version to server.
return ORBIS_OK;
}
/**
* Creates an NP request to determine if the user has a subscription to EA's services.
*/
s32 PS4_SYSV_ABI Func_F8E9DB52CD425743(UserService::OrbisUserServiceUserId user_id, bool* result) {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
if (result == nullptr) {
return ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_library_mutex};
// In the real library, this creates and sends a request that checks for EA subscription,
// then waits for the request to return a response, and returns that response.
// NP signed out likely returns an error, but I haven't figured out the error code yet.
// For now, stub having no subscription.
*result = false;
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("pMxXhNozUX8", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_A4CC5784DA33517F);
LIB_FUNCTION("pQfYTZHznMc", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_A507D84D91F39CC7);
LIB_FUNCTION("7CxI50-xlCk", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_EC2C48E74FF19429);
LIB_FUNCTION("+OnbUs1CV0M", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_F8E9DB52CD425743);
};
} // namespace Libraries::Np::NpPartner

View File

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpPartner {
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpPartner

View File

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
constexpr int ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED = 0x819d0001;
constexpr int ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT = 0x819d0002;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/rtc/rtc.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Np::NpTus {
using OrbisNpTssSlotId = s32;
using OrbisNpTusSlotId = s32;
struct OrbisNpTusVariable {
OrbisNpId npId;
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
u8 pad1[4];
OrbisNpId lastChangedAuthor;
s64 variable;
s64 oldVariable;
OrbisNpAccountId owner;
OrbisNpAccountId lastChangedAuthorId;
};
struct OrbisNpTusDataInfo {
u64 size;
u8 data[384];
};
struct OrbisNpTusDataStatus {
OrbisNpId npId;
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
OrbisNpId lastChangedAuthor;
u8 pad2[4];
void* data;
u64 dataSize;
OrbisNpTusDataInfo info;
};
static_assert(sizeof(OrbisNpTusDataStatus) == 0x1F0);
struct OrbisNpTusDataStatusA {
OrbisNpOnlineId onlineId;
u8 pad[16];
int set;
Libraries::Rtc::OrbisRtcTick lastChanged;
OrbisNpOnlineId lastChangedAuthor;
u8 pad2[20];
void* data;
u64 dataSize;
OrbisNpTusDataInfo info;
OrbisNpAccountId owner;
OrbisNpAccountId lastChangedAuthorId;
u8 pad3[16];
};
static_assert(sizeof(OrbisNpTusDataStatusA) == 0x210);
enum class OrbisNpTssStatus : int {
Ok = 0,
Partial = 1,
NotModified = 2,
};
struct OrbisNpTssDataStatus {
Libraries::Rtc::OrbisRtcTick modified;
OrbisNpTssStatus status;
u64 contentLength;
};
struct OrbisNpTssGetDataOptParam {
u64 size;
u64* offset;
u64* last;
void* param;
};
s32 PS4_SYSV_ABI sceNpTusWaitAsync(int reqId, int* result);
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpTus

View File

@ -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
@ -9,6 +9,8 @@
// For structs and constants shared between multiple Np libraries.
namespace Libraries::Np {
using OrbisNpAccountId = u64;
constexpr s32 ORBIS_NP_ONLINEID_MAX_LENGTH = 16;
struct OrbisNpOnlineId {
@ -43,4 +45,21 @@ struct OrbisNpIdToken {
u8 padding[7];
};
using OrbisNpServiceLabel = u32;
constexpr s32 ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF;
using OrbisNpAccountId = u64;
enum OrbisNpPlatformType : s32 {
None = 0,
PS3 = 1,
Vita = 2,
PS4 = 3,
};
struct OrbisNpPeerAddressA {
OrbisNpAccountId accountId;
OrbisNpPlatformType platform;
char padding[4];
};
}; // namespace Libraries::Np

View File

@ -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}", libCtxId, filterId);
return deletePushEventFilter(libCtxId, filterId);
}
s32 PS4_SYSV_ABI sceNpWebApiDeleteServicePushEventFilter(s32 libCtxId, s32 filterId) {
LOG_INFO(Lib_NpWebApi, "called, libCtxId = {:#x}, filterId = {:#x}", libCtxId, filterId);
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() {

View File

@ -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

View 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;

File diff suppressed because it is too large Load Diff

View 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

View File

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <mutex>
template <typename T, size_t N, int INVALID_OBJECT_ID_ERROR, int OBJECT_NOT_FOUND_ERROR,
int MAX_OBJECTS_ERROR>
struct ObjectManager {
s32 GetObject(int objectId, T** out) {
std::scoped_lock lk{mutex};
if (objectId < 1 || objectId > N) {
return INVALID_OBJECT_ID_ERROR;
}
auto obj = objects[objectId - 1];
if (!obj) {
return OBJECT_NOT_FOUND_ERROR;
}
*out = obj;
return ORBIS_OK;
}
template <typename... Args>
s32 CreateObject(Args&&... args) {
std::scoped_lock lk{mutex};
if (auto slot = std::ranges::find(objects, nullptr); slot != objects.end()) {
*slot = new T{args...};
return std::ranges::distance(objects.begin(), slot) + 1;
}
return MAX_OBJECTS_ERROR;
}
s32 DeleteObject(int objectId) {
std::scoped_lock lk{mutex};
if (objectId < 1 || objectId > N) {
return INVALID_OBJECT_ID_ERROR;
}
auto obj = objects[objectId - 1];
if (!obj) {
return OBJECT_NOT_FOUND_ERROR;
}
delete obj;
objects[objectId - 1] = nullptr;
return ORBIS_OK;
}
private:
std::mutex mutex;
std::array<T*, N> objects = {nullptr};
};

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
@ -96,18 +96,6 @@ int PS4_SYSV_ABI scePadGetCapability() {
int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) {
LOG_DEBUG(Lib_Pad, "called handle = {}", handle);
if (handle < 0) {
pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920;
pInfo->touchPadInfo.resolution.y = 950;
pInfo->stickInfo.deadZoneLeft = 1;
pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connected = false;
pInfo->deviceClass = OrbisPadDeviceClass::Standard;
return ORBIS_OK;
}
pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920;
pInfo->touchPadInfo.resolution.y = 950;
@ -115,8 +103,12 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connected = true;
pInfo->deviceClass = OrbisPadDeviceClass::Standard;
if (handle < 0) {
pInfo->connected = false;
return ORBIS_OK;
}
pInfo->connected = true;
if (EmulatorSettings::GetInstance()->IsUsingSpecialPad()) {
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
pInfo->deviceClass =
@ -305,24 +297,16 @@ int PS4_SYSV_ABI scePadOutputReport() {
return ORBIS_OK;
}
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
int connected_count = 0;
bool connected = false;
Input::State states[64];
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
auto const& controller = controllers[*controller_id];
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller, Input::State* states, s32 num, bool connected,
u32 connected_count) {
if (!connected) {
ret_num = 1;
pData[0] = {};
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
pData[0].connected = false;
return 1;
}
for (int i = 0; i < ret_num; i++) {
for (int i = 0; i < num; i++) {
pData[i].buttons = states[i].buttonsState;
pData[i].leftStick.x = states[i].axes[static_cast<int>(Input::Axis::LeftX)];
pData[i].leftStick.y = states[i].axes[static_cast<int>(Input::Axis::LeftY)];
@ -330,71 +314,67 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].rightStick.y = states[i].axes[static_cast<int>(Input::Axis::RightY)];
pData[i].analogButtons.l2 = states[i].axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData[i].analogButtons.r2 = states[i].axes[static_cast<int>(Input::Axis::TriggerRight)];
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
pData[i].acceleration.x = states[i].acceleration.x * 0.098;
pData[i].acceleration.y = states[i].acceleration.y * 0.098;
pData[i].acceleration.z = states[i].acceleration.z * 0.098;
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
if (handle == 1) {
const auto gyro_poll_rate = controller->accel_poll_rate;
const auto gyro_poll_rate = controller.accel_poll_rate;
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
now - controller->GetLastUpdate())
now - controller.GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
controller.SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
deltaTime, lastOrientation, outputOrientation);
pData[i].orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
controller.SetLastOrientation(outputOrientation);
}
}
pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
if (handle == 1) {
if (controller->GetTouchCount() >= 127) {
controller->SetTouchCount(0);
if (controller.GetTouchCount() >= 127) {
controller.SetTouchCount(0);
}
if (controller->GetSecondaryTouchCount() >= 127) {
controller->SetSecondaryTouchCount(0);
if (controller.GetSecondaryTouchCount() >= 127) {
controller.SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
controller->SetTouchCount(controller->GetTouchCount() + 1);
controller->SetSecondaryTouchCount(controller->GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
controller.SetTouchCount(controller.GetTouchCount() + 1);
controller.SetSecondaryTouchCount(controller.GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
} else {
if (controller->WasSecondaryTouchReset()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
controller->UnsetSecondaryTouchResetBool();
if (controller.WasSecondaryTouchReset()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
controller.UnsetSecondaryTouchResetBool();
}
}
}
controller->SetPreviousTouchNum(pData->touchData.touchNum);
controller.SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
}
} else {
states[i].touchpad[0].ID = 1;
@ -413,7 +393,22 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].deviceUniqueDataLen = 0;
}
return ret_num;
return num;
}
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
int connected_count = 0;
bool connected = false;
std::vector<Input::State> states(64);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
auto& controller = *controllers[*controller_id];
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected, connected_count);
}
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
@ -442,104 +437,17 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int connectedCount = 0;
bool isConnected = false;
Input::State state;
auto const& controller = controllers[*controller_id];
controller->ReadState(&state, &isConnected, &connectedCount);
pData->buttons = state.buttonsState;
auto getAxisValue = [&state, &controller](Input::Axis a) {
auto i = static_cast<int>(a);
if (controller->axis_smoothing_ticks[i] > 0) {
--controller->axis_smoothing_ticks[i];
return (state.axes[i] + controller->axis_smoothing_values[i]) / 2;
}
return state.axes[i];
};
pData->leftStick.x = getAxisValue(Input::Axis::LeftX);
pData->leftStick.y = getAxisValue(Input::Axis::LeftY);
pData->rightStick.x = getAxisValue(Input::Axis::RightX);
pData->rightStick.y = getAxisValue(Input::Axis::RightY);
pData->analogButtons.l2 = getAxisValue(Input::Axis::TriggerLeft);
pData->analogButtons.r2 = getAxisValue(Input::Axis::TriggerRight);
pData->acceleration.x = state.acceleration.x * 0.098;
pData->acceleration.y = state.acceleration.y * 0.098;
pData->acceleration.z = state.acceleration.z * 0.098;
pData->angularVelocity.x = state.angularVelocity.x;
pData->angularVelocity.y = state.angularVelocity.y;
pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
auto now = std::chrono::steady_clock::now();
float deltaTime =
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
lastOrientation, outputOrientation);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
// Only do this on handle 1 for now
if (handle == 1) {
if (controller->GetTouchCount() >= 127) {
controller->SetTouchCount(0);
}
if (controller->GetSecondaryTouchCount() >= 127) {
controller->SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
controller->SetTouchCount(controller->GetTouchCount() + 1);
controller->SetSecondaryTouchCount(controller->GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
} else {
if (controller->WasSecondaryTouchReset()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
controller->UnsetSecondaryTouchResetBool();
}
}
}
controller->SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
state.touchpad[0].ID = controller->GetTouchCount();
state.touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
state.touchpad[0].ID = controller->GetTouchCount();
state.touchpad[1].ID = controller->GetSecondaryTouchCount();
}
} else {
state.touchpad[0].ID = 1;
state.touchpad[1].ID = 2;
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
pData->touchData.touch[0].x = state.touchpad[0].x;
pData->touchData.touch[0].y = state.touchpad[0].y;
pData->touchData.touch[0].id = state.touchpad[0].ID;
pData->touchData.touch[1].x = state.touchpad[1].x;
pData->touchData.touch[1].y = state.touchpad[1].y;
pData->touchData.touch[1].id = state.touchpad[1].ID;
pData->timestamp = state.time;
pData->connected = true; // isConnected; //TODO fix me proper
pData->connectedCount = 1; // connectedCount;
pData->deviceUniqueDataLen = 0;
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
auto& controller = *controllers[*controller_id];
int connected_count = 0;
bool connected = false;
Input::State state;
controller.ReadState(&state, &connected, &connected_count);
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
return ORBIS_OK;
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
@ -106,7 +106,7 @@ void Linker::Execute(const std::vector<std::string>& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
Common::SetCurrentThreadName("Game:Main");
if (auto& ipc = IPC::Instance()) {
ipc.WaitForStart();
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
@ -74,12 +74,13 @@ 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;
}
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -118,6 +119,7 @@ void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
}
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -138,6 +140,7 @@ void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
bool MemoryManager::TryWriteBacking(void* address, const void* data, u64 size) {
const VAddr virtual_addr = std::bit_cast<VAddr>(address);
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -175,7 +178,7 @@ bool MemoryManager::TryWriteBacking(void* address, const void* data, u64 size) {
}
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
alignment = alignment > 0 ? alignment : 64_KB;
auto dmem_area = FindDmemArea(search_start);
@ -217,7 +220,7 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size,
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment,
s32 memory_type) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
alignment = alignment > 0 ? alignment : 16_KB;
auto dmem_area = FindDmemArea(search_start);
@ -264,9 +267,7 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) {
return ORBIS_OK;
}
// Lock mutex
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
// If this is a checked free, then all direct memory in range must be allocated.
std::vector<std::pair<PAddr, u64>> free_list;
u64 remaining_size = size;
@ -317,6 +318,17 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) {
}
}
}
// Early unmap from GPU to avoid deadlocking.
for (auto& [addr, unmap_size] : remove_list) {
if (IsValidGpuMapping(addr, unmap_size)) {
rasterizer->UnmapMemory(addr, unmap_size);
}
}
// Acquire writer lock
std::scoped_lock lk2{mutex};
for (const auto& [addr, size] : remove_list) {
LOG_INFO(Kernel_Vmm, "Unmapping direct mapping {:#x} with size {:#x}", addr, size);
UnmapMemoryImpl(addr, size);
@ -338,7 +350,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};
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);
@ -423,6 +436,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);
}
@ -430,54 +444,31 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32
return ORBIS_OK;
}
std::pair<s32, MemoryManager::VMAHandle> MemoryManager::CreateArea(
VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type,
std::string_view name, u64 alignment) {
// Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues.
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
// On a PS4, the Fixed flag is ignored if address 0 is provided.
if (True(flags & MemoryMapFlags::Fixed) && virtual_addr != 0) {
ASSERT_MSG(IsValidMapping(mapped_addr, size), "Attempted to access invalid address {:#x}",
mapped_addr);
auto vma = FindVMA(mapped_addr)->second;
// There's a possible edge case where we're mapping to a partially reserved range.
// To account for this, unmap any reserved areas within this mapping range first.
auto unmap_addr = mapped_addr;
MemoryManager::VMAHandle MemoryManager::CreateArea(VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, VMAType type,
std::string_view name, u64 alignment) {
// Locate the VMA representing the requested region
auto vma = FindVMA(virtual_addr)->second;
if (True(flags & MemoryMapFlags::Fixed)) {
// If fixed is specified, map directly to the region of virtual_addr + size.
// Callers should check to ensure the NoOverwrite flag is handled appropriately beforehand.
auto unmap_addr = virtual_addr;
auto unmap_size = size;
// If flag NoOverwrite is provided, don't overwrite mapped VMAs.
// When it isn't provided, VMAs can be overwritten regardless of if they're mapped.
while ((False(flags & MemoryMapFlags::NoOverwrite) || vma.IsFree()) &&
unmap_addr < mapped_addr + size) {
while (unmap_size > 0) {
auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size);
unmap_addr += unmapped;
unmap_size -= unmapped;
vma = FindVMA(unmap_addr)->second;
}
vma = FindVMA(mapped_addr)->second;
auto remaining_size = vma.base + vma.size - mapped_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr);
return {ORBIS_KERNEL_ERROR_ENOMEM, vma_map.end()};
}
} else {
// When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
// search from address 0x200000000 instead.
alignment = alignment > 0 ? alignment : 16_KB;
mapped_addr = virtual_addr == 0 ? 0x200000000 : mapped_addr;
mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) {
// No suitable memory areas to map to
return {ORBIS_KERNEL_ERROR_ENOMEM, vma_map.end()};
}
}
vma = FindVMA(virtual_addr)->second;
// By this point, vma should be free and ready to map.
// Caller performs address searches for non-fixed mappings before this.
ASSERT_MSG(vma.IsFree(), "VMA to map is not free");
// Create a memory area representing this mapping.
const auto new_vma_handle = CarveVMA(mapped_addr, size);
const auto new_vma_handle = CarveVMA(virtual_addr, size);
auto& new_vma = new_vma_handle->second;
const bool is_exec = True(prot & MemoryProt::CpuExec);
if (True(prot & MemoryProt::CpuWrite)) {
@ -485,12 +476,13 @@ std::pair<s32, MemoryManager::VMAHandle> MemoryManager::CreateArea(
prot |= MemoryProt::CpuRead;
}
// Update VMA appropriately.
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = prot;
new_vma.name = name;
new_vma.type = type;
new_vma.phys_areas.clear();
return {ORBIS_OK, new_vma_handle};
return new_vma_handle;
}
s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
@ -505,8 +497,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
total_flexible_size - flexible_usage, size);
return ORBIS_KERNEL_ERROR_EINVAL;
}
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
PhysHandle dmem_area;
// Validate the requested physical address range
@ -539,12 +530,37 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
}
}
auto [result, new_vma_handle] =
CreateArea(virtual_addr, size, prot, flags, type, name, alignment);
if (result != ORBIS_OK) {
return result;
if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) {
// Perform necessary error checking for Fixed & NoOverwrite case
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
auto vma = FindVMA(virtual_addr)->second;
auto remaining_size = vma.base + vma.size - virtual_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, virtual_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
} else if (False(flags & MemoryMapFlags::Fixed)) {
// Find a free virtual addr to map
alignment = alignment > 0 ? alignment : 16_KB;
virtual_addr = virtual_addr == 0 ? DEFAULT_MAPPING_BASE : virtual_addr;
virtual_addr = SearchFree(virtual_addr, size, alignment);
if (virtual_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Acquire writer lock.
std::unique_lock lk2{mutex};
// Create VMA representing this mapping.
auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment);
auto& new_vma = new_vma_handle->second;
auto mapped_addr = new_vma.base;
bool is_exec = True(prot & MemoryProt::CpuExec);
@ -580,7 +596,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;
@ -589,13 +608,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;
dmem_area = FindDmemArea(phys_addr);
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);
@ -604,17 +623,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");
}
@ -634,19 +653,22 @@ 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);
}
}
return ORBIS_OK;
}
s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, s32 fd, s64 phys_addr) {
std::scoped_lock lk{mutex};
uintptr_t handle = 0;
std::scoped_lock lk{unmap_mutex};
// Get the file to map
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(fd);
if (file == nullptr) {
@ -664,12 +686,13 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
prot |= MemoryProt::CpuRead;
}
const auto handle = file->f.GetFileMapping();
handle = file->f.GetFileMapping();
if (False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Write) ||
False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Append)) {
// If the file does not have write access, ensure prot does not contain write permissions.
// On real hardware, these mappings succeed, but the memory cannot be written to.
// If the file does not have write access, ensure prot does not contain write
// permissions. On real hardware, these mappings succeed, but the memory cannot be
// written to.
prot &= ~MemoryProt::CpuWrite;
}
@ -683,13 +706,38 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
prot &= ~MemoryProt::CpuExec;
}
auto [result, new_vma_handle] =
CreateArea(virtual_addr, size, prot, flags, VMAType::File, "anon", 0);
if (result != ORBIS_OK) {
return result;
if (True(flags & MemoryMapFlags::Fixed) && False(flags & MemoryMapFlags::NoOverwrite)) {
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
auto vma = FindVMA(virtual_addr)->second;
auto remaining_size = vma.base + vma.size - virtual_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, virtual_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
} else if (False(flags & MemoryMapFlags::Fixed)) {
virtual_addr = virtual_addr == 0 ? DEFAULT_MAPPING_BASE : virtual_addr;
virtual_addr = SearchFree(virtual_addr, size, 16_KB);
if (virtual_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Aquire writer lock
std::scoped_lock lk2{mutex};
// Update VMA map and map to address space.
auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, VMAType::File, "anon", 0);
auto& new_vma = new_vma_handle->second;
new_vma.fd = fd;
auto mapped_addr = new_vma.base;
bool is_exec = True(prot & MemoryProt::CpuExec);
@ -700,7 +748,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
}
s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -714,6 +762,14 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
it++;
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Aquire writer mutex
std::scoped_lock lk2{mutex};
// Loop through all vmas in the area, unmap them.
u64 remaining_size = size;
VAddr current_addr = virtual_addr;
@ -722,13 +778,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
const auto& vma_base = handle->second;
const auto start_in_vma = current_addr - vma_base.base;
const auto size_in_vma = std::min<u64>(remaining_size, vma_base.size - start_in_vma);
if (vma_base.type == VMAType::Pooled) {
// We always map PoolCommitted memory to GPU, so unmap when decomitting.
if (IsValidGpuMapping(current_addr, size_in_vma)) {
rasterizer->UnmapMemory(current_addr, size_in_vma);
}
// Track how much pooled memory is decommitted
pool_budget += size_in_vma;
@ -773,7 +823,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
}
// Unmap from address space
impl.Unmap(virtual_addr, size, true);
impl.Unmap(virtual_addr, size);
// Tracy memory tracking breaks from merging memory areas. Disabled for now.
// TRACK_FREE(virtual_addr, "VMEM");
@ -784,29 +834,32 @@ s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) {
if (size == 0) {
return ORBIS_OK;
}
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
// Align address and size appropriately
virtual_addr = Common::AlignDown(virtual_addr, 16_KB);
size = Common::AlignUp(size, 16_KB);
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
u64 bytes_unmapped = UnmapMemoryImpl(virtual_addr, size);
return bytes_unmapped;
// If the requested range has GPU access, unmap from GPU.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Acquire writer lock.
std::scoped_lock lk2{mutex};
return UnmapMemoryImpl(virtual_addr, size);
}
u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size) {
const auto start_in_vma = virtual_addr - vma_base.base;
const auto size_in_vma = std::min<u64>(vma_base.size - start_in_vma, size);
const auto vma_type = vma_base.type;
const bool has_backing = HasPhysicalBacking(vma_base) || vma_base.type == VMAType::File;
const bool readonly_file =
vma_base.prot == MemoryProt::CpuRead && vma_base.type == VMAType::File;
const bool is_exec = True(vma_base.prot & MemoryProt::CpuExec);
if (vma_base.type == VMAType::Free || vma_base.type == VMAType::Pooled) {
return size_in_vma;
}
PAddr phys_base = 0;
VAddr current_addr = virtual_addr;
if (vma_base.phys_areas.size() > 0) {
u64 size_to_free = size_in_vma;
@ -861,14 +914,9 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
if (vma_type != VMAType::Reserved && vma_type != VMAType::PoolReserved) {
// Unmap the memory region.
impl.Unmap(virtual_addr, size_in_vma, has_backing);
impl.Unmap(virtual_addr, size_in_vma);
// Tracy memory tracking breaks from merging memory areas. Disabled for now.
// TRACK_FREE(virtual_addr, "VMEM");
// If this mapping has GPU access, unmap from GPU.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
}
return size_in_vma;
}
@ -984,7 +1032,7 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
}
// Ensure the range to modify is valid
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
// Appropriately restrict flags.
@ -1142,7 +1190,7 @@ s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u6
}
s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
@ -1189,7 +1237,7 @@ s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) {
}
void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
// Sizes are aligned up to the nearest 16_KB
u64 aligned_size = Common::AlignUp(size, 16_KB);
@ -1247,7 +1295,6 @@ s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) {
ASSERT_MSG(IsValidMapping(addr), "Attempted to access invalid address {:#x}", addr);
const auto& vma = FindVMA(addr)->second;
if (vma.IsFree()) {
mutex.unlock_shared();
return ORBIS_KERNEL_ERROR_EACCES;
}

View File

@ -28,6 +28,8 @@ class MemoryMapViewer;
namespace Core {
constexpr u64 DEFAULT_MAPPING_BASE = 0x200000000;
enum class MemoryProt : u32 {
NoAccess = 0,
CpuRead = 1,
@ -304,10 +306,8 @@ private:
vma.type == VMAType::Pooled;
}
std::pair<s32, MemoryManager::VMAHandle> CreateArea(VAddr virtual_addr, u64 size,
MemoryProt prot, MemoryMapFlags flags,
VMAType type, std::string_view name,
u64 alignment);
VMAHandle CreateArea(VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags,
VMAType type, std::string_view name, u64 alignment);
VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment);
@ -333,6 +333,7 @@ private:
PhysMap fmem_map;
VMAMap vma_map;
Common::SharedFirstMutex mutex{};
std::mutex unmap_mutex{};
u64 total_direct_size{};
u64 total_flexible_size{};
u64 flexible_usage{};

View File

@ -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);

View File

@ -29,6 +29,7 @@
#include "core/debugger.h"
#include "core/devtools/widget/module_list.h"
#include "core/emulator_settings.h"
#include "core/emulator_state.h"
#include "core/file_format/psf.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
@ -37,6 +38,7 @@
#include "core/libraries/font/fontft.h"
#include "core/libraries/jpeg/jpegenc.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libpng/pngenc.h"
#include "core/libraries/libs.h"
#include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np/np_trophy.h"
@ -97,7 +99,7 @@ s32 ReadCompiledSdkVersion(const std::filesystem::path& file) {
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::optional<std::filesystem::path> p_game_folder) {
Common::SetCurrentThreadName("Main Thread");
Common::SetCurrentThreadName("shadPS4:Main");
if (waitForDebuggerBeforeRun) {
Debugger::WaitForDebuggerAttach();
}
@ -206,6 +208,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() && EmulatorSettings::GetInstance()->IsSeparateLoggingEnabled()) {
Common::Log::Initialize(id + ".log");
@ -546,7 +555,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
{"libSceRtc.sprx", &Libraries::Rtc::RegisterLib},
{"libSceJpegDec.sprx", nullptr},
{"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib},
{"libScePngEnc.sprx", nullptr},
{"libScePngEnc.sprx", &Libraries::PngEnc::RegisterLib},
{"libSceJson.sprx", nullptr},
{"libSceJson2.sprx", nullptr},
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_set>
@ -17,143 +17,100 @@ static std::string SelectedGamepad = "";
namespace Input {
GameController::GameController() {
m_states_num = 0;
m_last_state = State();
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
}
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
std::scoped_lock lock{m_mutex};
void State::OnAxis(Axis axis, int value) {
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
axes[static_cast<int>(axis)] = value;
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
touchpad[touchIndex].state = isDown;
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
touchpad[touchIndex].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
GameController::GameController() : m_states_queue(64) {}
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
*isConnected = m_connected;
*connectedCount = m_connected_count;
*state = GetLastState();
*state = m_state;
}
int GameController::ReadStates(State* states, int states_num, bool* isConnected,
int* connectedCount) {
std::scoped_lock lock{m_mutex};
*isConnected = m_connected;
*connectedCount = m_connected_count;
int ret_num = 0;
if (m_connected) {
if (m_states_num == 0) {
ret_num = 1;
states[0] = m_last_state;
} else {
for (uint32_t i = 0; i < m_states_num; i++) {
if (ret_num >= states_num) {
break;
}
auto index = (m_first_state + i) % MAX_STATES;
if (!m_private[index].obtained) {
m_private[index].obtained = true;
states[ret_num++] = m_states[index];
}
std::lock_guard lg(m_states_queue_mutex);
for (int i = 0; i < states_num; i++) {
auto o_state = m_states_queue.Pop();
if (!o_state) {
break;
}
states[ret_num++] = *o_state;
}
}
return ret_num;
}
State GameController::GetLastState() const {
if (m_states_num == 0) {
return m_last_state;
}
const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES;
auto copy = m_states[last];
return copy;
}
void GameController::AddState(const State& state) {
if (m_states_num >= MAX_STATES) {
m_states_num = MAX_STATES - 1;
m_first_state = (m_first_state + 1) % MAX_STATES;
}
const u32 index = (m_first_state + m_states_num) % MAX_STATES;
m_states[index] = state;
m_last_state = state;
m_private[index].obtained = false;
m_states_num++;
}
void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button,
bool is_pressed) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
if (is_pressed) {
state.buttonsState |= button;
} else {
state.buttonsState &= ~button;
}
AddState(state);
void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
m_state.OnButton(button, is_pressed);
PushState();
}
void GameController::Axis(int id, Input::Axis axis, int value) {
using Libraries::Pad::OrbisPadButtonDataOffset;
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
int axis_id = static_cast<int>(axis);
if (std::abs(state.axes[axis_id] - value) > 120) {
LOG_DEBUG(Input, "Keyboard axis change detected");
axis_smoothing_ticks[axis_id] = GameController::max_smoothing_ticks;
axis_smoothing_values[axis_id] = state.axes[axis_id];
}
state.axes[axis_id] = value;
if (axis == Input::Axis::TriggerLeft) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::L2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::L2;
}
}
if (axis == Input::Axis::TriggerRight) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::R2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::R2;
}
}
AddState(state);
m_state.OnAxis(axis, value);
PushState();
}
void GameController::Gyro(int id, const float gyro[3]) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the angular velocity (gyro data)
state.angularVelocity.x = gyro[0]; // X-axis
state.angularVelocity.y = gyro[1]; // Y-axis
state.angularVelocity.z = gyro[2]; // Z-axis
AddState(state);
m_state.OnGyro(gyro);
PushState();
}
void GameController::Acceleration(int id, const float acceleration[3]) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the acceleration values
state.acceleration.x = acceleration[0]; // X-axis
state.acceleration.y = acceleration[1]; // Y-axis
state.acceleration.z = acceleration[2]; // Z-axis
AddState(state);
m_state.OnAccel(acceleration);
PushState();
}
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
@ -207,15 +164,8 @@ bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.touchpad[touchIndex].state = touchDown;
state.touchpad[touchIndex].x = static_cast<u16>(x * 1920);
state.touchpad[touchIndex].y = static_cast<u16>(y * 941);
AddState(state);
m_state.OnTouchpad(touchIndex, touchDown, x, y);
PushState();
}
}
@ -304,22 +254,18 @@ void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
}
}
u8 GameController::GetTouchCount() {
std::scoped_lock lock{m_mutex};
return m_touch_count;
}
void GameController::SetTouchCount(u8 touchCount) {
std::scoped_lock lock{m_mutex};
m_touch_count = touchCount;
}
u8 GameController::GetSecondaryTouchCount() {
std::scoped_lock lock{m_mutex};
return m_secondary_touch_count;
}
void GameController::SetSecondaryTouchCount(u8 touchCount) {
std::scoped_lock lock{m_mutex};
m_secondary_touch_count = touchCount;
if (touchCount == 0) {
m_was_secondary_reset = true;
@ -327,63 +273,49 @@ void GameController::SetSecondaryTouchCount(u8 touchCount) {
}
u8 GameController::GetPreviousTouchNum() {
std::scoped_lock lock{m_mutex};
return m_previous_touchnum;
}
void GameController::SetPreviousTouchNum(u8 touchNum) {
std::scoped_lock lock{m_mutex};
m_previous_touchnum = touchNum;
}
bool GameController::WasSecondaryTouchReset() {
std::scoped_lock lock{m_mutex};
return m_was_secondary_reset;
}
void GameController::UnsetSecondaryTouchResetBool() {
std::scoped_lock lock{m_mutex};
m_was_secondary_reset = false;
}
void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) {
std::scoped_lock lock{m_mutex};
m_orientation = orientation;
}
Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() {
std::scoped_lock lock{m_mutex};
return m_orientation;
}
std::chrono::steady_clock::time_point GameController::GetLastUpdate() {
std::scoped_lock lock{m_mutex};
return m_last_update;
}
void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) {
std::scoped_lock lock{m_mutex};
m_last_update = lastUpdate;
}
void GameController::PushState() {
std::lock_guard lg(m_states_queue_mutex);
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
m_states_queue.Push(m_state);
}
u32 GameController::Poll() {
std::scoped_lock lock{m_mutex};
if (m_connected) {
auto time = Libraries::Kernel::sceKernelGetProcessTime();
if (m_states_num == 0) {
auto diff = (time - m_last_state.time) / 1000;
if (diff >= 100) {
AddState(GetLastState());
}
} else {
auto index = (m_first_state - 1 + m_states_num) % MAX_STATES;
auto diff = (time - m_states[index].time) / 1000;
if (m_private[index].obtained && diff >= 100) {
AddState(GetLastState());
}
}
PushState();
}
return 100;
return 33;
}
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {

View File

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <vector>
#include <SDL3/SDL_gamepad.h>
#include "SDL3/SDL_joystick.h"
#include "common/assert.h"
@ -47,7 +49,36 @@ inline int GetAxis(int min, int max, int value) {
return (v < 0 ? 0 : (v > 255 ? 255 : v));
}
constexpr u32 MAX_STATES = 32;
template <class T>
class RingBufferQueue {
public:
RingBufferQueue(size_t size) : m_storage(size) {}
void Push(T item) {
const size_t index = (m_begin + m_size) % m_storage.size();
m_storage[index] = std::move(item);
if (m_size < m_storage.size()) {
m_size += 1;
} else {
m_begin = (m_begin + 1) % m_storage.size();
}
}
std::optional<T> Pop() {
if (m_size == 0) {
return {};
}
const size_t index = m_begin;
m_begin = (m_begin + 1) % m_storage.size();
m_size -= 1;
return std::move(m_storage[index]);
}
private:
size_t m_begin = 0;
size_t m_size = 0;
std::vector<T> m_storage;
};
class GameController {
friend class GameControllers;
@ -58,9 +89,8 @@ public:
void ReadState(State* state, bool* isConnected, int* connectedCount);
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
State GetLastState() const;
void CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
void AddState(const State& state);
void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
void Axis(int id, Input::Axis axis, int value);
void Gyro(int id, const float gyro[3]);
void Acceleration(int id, const float acceleration[3]);
@ -97,27 +127,22 @@ public:
int axis_smoothing_values[static_cast<int>(Input::Axis::AxisMax)]{0};
private:
struct StateInternal {
bool obtained = false;
};
void PushState();
std::mutex m_mutex;
bool m_connected = true;
State m_last_state;
int m_connected_count = 0;
u32 m_states_num = 0;
u32 m_first_state = 0;
int m_connected_count = 1;
u8 m_touch_count = 0;
u8 m_secondary_touch_count = 0;
u8 m_previous_touch_count = 0;
u8 m_previous_touchnum = 0;
bool m_was_secondary_reset = false;
std::array<State, MAX_STATES> m_states;
std::array<StateInternal, MAX_STATES> m_private;
std::chrono::steady_clock::time_point m_last_update = {};
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
u8 player_index = -1;
State m_state;
std::mutex m_states_queue_mutex;
RingBufferQueue<State> m_states_queue;
};
class GameControllers {

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "input_handler.h"
@ -23,6 +23,8 @@
#include "common/io_file.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/devtools/layer.h"
#include "core/emulator_state.h"
#include "input/controller.h"
#include "input/input_mouse.h"
@ -155,6 +157,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);
@ -183,8 +187,8 @@ std::optional<int> parseInt(const std::string& s) {
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
@ -375,21 +379,24 @@ 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()) {
// todo add new shit here
connection = BindingConnection(
binding,
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(button_it->second)));
binding, &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, 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_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, ControllerOutput(hotkey_it->second)));
connections.insert(connections.end(), connection);
} else if (axis_it != string_to_axis_map.end()) {
// todo add new shit here
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
@ -553,20 +560,21 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
}
old_button_state = new_button_state;
old_param = *new_param;
bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed();
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
auto controller = controllers[gamepad_index];
switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
case LEFTJOYSTICK_HALFMODE:
leftjoystick_halfmode = new_button_state;
@ -574,6 +582,9 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
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;
@ -583,9 +594,6 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
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;
@ -603,6 +611,15 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
break;
case HOTKEY_REMOVE_VIRTUAL_USER:
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
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);
@ -614,7 +631,7 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
SetMouseGyroRollMode(new_button_state);
break;
default: // is a normal key (hopefully)
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
}
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
@ -645,13 +662,13 @@ void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
case Axis::TriggerLeft:
ApplyDeadzone(new_param, lefttrigger_deadzone);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2,
controllers[gamepad_index]->Button(0, OrbisPadButtonDataOffset::L2,
*new_param > 0x20);
return;
case Axis::TriggerRight:
ApplyDeadzone(new_param, righttrigger_deadzone);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2,
controllers[gamepad_index]->Button(0, OrbisPadButtonDataOffset::R2,
*new_param > 0x20);
return;
default:

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -56,10 +56,12 @@
#define HOTKEY_RELOAD_INPUTS 0xf0000005
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
#define HOTKEY_RENDERDOC 0xf0000008
#define HOTKEY_ADD_VIRTUAL_USER 0xf0000009
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000a
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf000000b
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
#define HOTKEY_RENDERDOC 0xf0000009
#define HOTKEY_VOLUME_UP 0xf000000a
#define HOTKEY_VOLUME_DOWN 0xf000000b
#define HOTKEY_ADD_VIRTUAL_USER 0xf000000c
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d
#define SDL_UNMAPPED UINT32_MAX - 1
@ -144,6 +146,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},
@ -155,6 +159,8 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
};
const std::map<std::string, AxisMapping> string_to_axis_map = {

View File

@ -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 <cmath>
@ -104,8 +104,8 @@ void EmulateTouchpad(GameController* controller, u32 interval) {
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
controller->CheckButton(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
}
void ApplyMouseInputBlockers() {

View File

@ -45,11 +45,16 @@ int main(int argc, char* argv[]) {
// ---- Trophy key migration ----
auto key_manager = KeyManager::GetInstance();
key_manager->LoadFromFile();
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() &&
!Config::getTrophyKey().empty()) {
key_manager->SetAllKeys({.TrophyKeySet = {.ReleaseTrophyKey = KeyManager::HexStringToBytes(
Config::getTrophyKey())}});
key_manager->SaveToFile();
auto keys = key_manager->GetAllKeys();
if (keys.TrophyKeySet.ReleaseTrophyKey.empty() && !Config::getTrophyKey().empty()) {
keys.TrophyKeySet.ReleaseTrophyKey =
KeyManager::HexStringToBytes(Config::getTrophyKey());
key_manager->SetAllKeys(keys);
key_manager->SaveToFile();
}
}
CLI::App app{"shadPS4 Emulator CLI"};

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "SDL3/SDL_events.h"
@ -68,9 +68,11 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
}
}
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
static Uint32 SDLCALL PollGyroAndAccel(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
return controller->Poll();
controller->Gyro(0);
controller->Acceleration(0);
return 4;
}
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
@ -198,23 +200,9 @@ void WindowSDL::WaitEvent() {
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
OnGamepadEvent(&event);
break;
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
int controller_id =
Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which);
switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO:
controllers[controller_id]->Gyro(0, event.gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controllers[controller_id]->Acceleration(0, event.gsensor.data);
break;
default:
break;
}
break;
}
case SDL_EVENT_QUIT:
is_open = false;
break;
@ -292,7 +280,7 @@ void WindowSDL::WaitEvent() {
void WindowSDL::InitTimers() {
for (int i = 0; i < 4; ++i) {
SDL_AddTimer(250, &PollController, controllers[i]);
SDL_AddTimer(4, &PollGyroAndAccel, controllers[i]);
}
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
}
@ -363,7 +351,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
// You can still bind other things to it though
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gbutton.which)]
->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down);
return;
}
@ -372,11 +360,11 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
switch ((SDL_SensorType)event->gsensor.sensor) {
case SDL_SENSOR_GYRO:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Gyro(0, event->gsensor.data);
->UpdateGyro(0, event->gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Acceleration(0, event->gsensor.data);
->UpdateAcceleration(0, event->gsensor.data);
break;
default:
break;

Some files were not shown because too many files have changed in this diff Show More