Merge branch 'main' into gr2fix

This commit is contained in:
Valdis Bogdāns 2026-02-01 22:21:07 +02:00 committed by GitHub
commit 4c13a797fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 3409 additions and 157 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

@ -524,6 +524,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
@ -584,10 +587,14 @@ 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
@ -602,6 +609,8 @@ 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
)
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp
@ -919,6 +928,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
src/shader_recompiler/ir/passes/hull_shader_transform.cpp
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp
src/shader_recompiler/ir/passes/ir_passes.h
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp

View File

@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
@ -58,6 +58,11 @@ This project began for fun. Given our limited free time, it may take some time b
# Building
## Docker
For building shadPS4 in a containerized environment using Docker and VSCode, check the instructions here:
[**Docker Build Instructions**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-docker.md)
## Windows
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).

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

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
{
"name": "shadPS4-dev",
"dockerComposeFile": [
"../docker-compose.yml"
],
"containerEnv": {
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
"GITHUB_USER": "${localEnv:GITHUB_USER}"
},
"service": "shadps4",
"workspaceFolder": "/workspaces/shadPS4",
"remoteUser": "root",
"shutdownAction": "none",
"customizations": {
"vscode": {
"extensions": [
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"xaver.clang-format"
],
"settings": {
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--header-insertion=never",
"--compile-commands-dir=/workspaces/shadPS4/Build/x64-Clang-Release"
],
"C_Cpp.intelliSenseEngine": "Disabled"
}
}
},
"settings": {
"cmake.configureOnOpen": false,
"cmake.generator": "Unix Makefiles",
"cmake.environment": {
"CC": "clang",
"CXX": "clang++"
},
"cmake.configureEnvironment": {
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
},
"editor.formatOnSave": true,
"clang-format.executable": "clang-format-19"
}
}

View File

@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
FROM archlinux:latest
RUN pacman-key --init && \
pacman-key --populate archlinux && \
pacman -Syu --noconfirm
RUN pacman -S --noconfirm \
base-devel \
clang \
clang19 \
ninja \
git \
ca-certificates \
wget \
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

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
services:
shadps4:
build:
context: ./.docker
volumes:
- ./emu:/workspaces/shadPS4:cached
tty: true

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

@ -0,0 +1,100 @@
<!--
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
# Building shadPS4 with Docker and VSCode Support
This guide explains how to build **shadPS4** using Docker while keeping full compatibility with **VSCode** development.
---
## Prerequisites
Before starting, ensure you have:
- **Docker Engine** or **Docker Desktop** installed
[Installation Guide](https://docs.docker.com/engine/install/)
- **Git** installed on your system.
---
## Step 1: Prepare the Docker Environment
Inside the container (or on your host if mounting volumes):
1. Navigate to the repository folder containing the Docker Builder folder:
```bash
cd <path-to-repo>
```
2. Start the Docker container:
```bash
docker compose up -d
```
This will spin up a container with all the necessary build dependencies, including Clang, CMake, SDL2, Vulkan, and more.
## Step 2: Clone shadPS4 Source
```bash
mkdir emu
cd emu
git clone --recursive https://github.com/shadps4-emu/shadPS4.git .
or your fork link.
```
3. Initialize submodules:
```bash
git submodule update --init --recursive
```
## 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:
```bash
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
Then build the project:
```bash
cmake --build ./build --parallel $(nproc)
```
* Tip: To enable debug builds, add -DCMAKE_BUILD_TYPE=Debug to the CMake command.
---
After a successful build, the executable is located at:
```bash
./build/shadps4
```
## Step 4: VSCode Integration
1. Open the repository in VSCode.
2. The CMake Tools extension should automatically detect the build directory inside the container or on your host.
3. You can configure build options, build, and debug directly from the VSCode interface without extra manual setup.
# Notes
* The Docker environment contains all dependencies, so you dont need to install anything manually.
* Using Clang inside Docker ensures consistent builds across Linux and macOS runners.
* GitHub Actions are recommended for cross-platform builds, including Windows .exe output, which is not trivial to produce locally without Visual Studio or clang-cl.

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

@ -204,6 +204,7 @@ add_subdirectory(tracy)
# pugixml
if (NOT TARGET pugixml::pugixml)
option(PUGIXML_NO_EXCEPTIONS "" ON)
add_subdirectory(pugixml)
endif()
@ -273,4 +274,4 @@ add_subdirectory(miniz)
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory(ext-CLI11)
add_subdirectory(CLI11)

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

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

@ -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
@ -111,6 +113,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 = std::string{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 = std::string{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 = std::string{name};
}
// Do Nothing on MinGW
}

View File

@ -709,7 +709,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;
@ -792,12 +792,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

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

@ -1262,7 +1262,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

@ -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"
@ -325,6 +325,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

@ -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"
@ -97,6 +100,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 +109,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

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

@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 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"
@ -17,6 +19,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,
@ -665,6 +670,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");
@ -682,8 +700,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;
}
@ -692,6 +724,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;
@ -701,6 +767,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 = Config::getPSNSignedIn();
@ -739,9 +821,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,11 @@ struct OrbisNpCreateAsyncRequestParameter {
u8 padding[4];
};
void RegisterNpCallback(std::string key, std::function<void()> cb);
void DeregisterNpCallback(std::string key);
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::ORBIS_NP_PLATFORM_TYPE_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,158 @@
// SPDX-FileCopyrightText: Copyright 2024-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::NpTus {
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariable();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetData();
s32 PS4_SYSV_ABI sceNpTusGetDataAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariable();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariable();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetData();
s32 PS4_SYSV_ABI sceNpTusSetDataAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataVUser();
s32 PS4_SYSV_ABI sceNpTusSetDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariable();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA();
s32 PS4_SYSV_ABI sceNpTssGetData();
s32 PS4_SYSV_ABI sceNpTssGetDataAsync();
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage();
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync();
s32 PS4_SYSV_ABI sceNpTssGetStorage();
s32 PS4_SYSV_ABI sceNpTssGetStorageAsync();
s32 PS4_SYSV_ABI sceNpTusAbortRequest();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableA();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners();
s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA();
s32 PS4_SYSV_ABI sceNpTusCreateRequest();
s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataA();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUser();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteRequest();
s32 PS4_SYSV_ABI sceNpTusGetDataA();
s32 PS4_SYSV_ABI sceNpTusGetDataAAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataAVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableA();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableA();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusPollAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataA();
s32 PS4_SYSV_ABI sceNpTusSetDataAAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataAVUser();
s32 PS4_SYSV_ABI sceNpTusSetDataAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetThreadParam();
s32 PS4_SYSV_ABI sceNpTusSetTimeout();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableA();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusWaitAsync();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpTus

View File

@ -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,19 @@ struct OrbisNpIdToken {
u8 padding[7];
};
using OrbisNpServiceLabel = u32;
enum class OrbisNpPlatformType : s32 {
ORBIS_NP_PLATFORM_TYPE_NONE = 0,
ORBIS_NP_PLATFORM_TYPE_PS3 = 1,
ORBIS_NP_PLATFORM_TYPE_VITA = 2,
ORBIS_NP_PLATFORM_TYPE_PS4 = 3,
};
struct OrbisNpPeerAddressA {
OrbisNpAccountId accountId;
OrbisNpPlatformType platformType;
u8 padding[4];
};
}; // namespace Libraries::Np

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/alignment.h"
@ -105,7 +105,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

@ -79,6 +79,7 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
return size;
}
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -117,6 +118,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);
@ -137,6 +139,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);
@ -174,7 +177,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);
@ -216,7 +219,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);
@ -263,9 +266,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;
@ -316,6 +317,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);
@ -337,7 +349,7 @@ 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{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -429,54 +441,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)) {
@ -484,12 +473,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,
@ -504,8 +494,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
@ -538,12 +527,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::scoped_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);
@ -590,7 +604,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
// Map the physical memory for this direct memory mapping.
auto phys_addr_to_search = 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.
@ -638,14 +652,15 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
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) {
@ -663,12 +678,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;
}
@ -682,13 +698,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);
@ -699,7 +740,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);
@ -713,6 +754,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;
@ -721,13 +770,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;
@ -772,7 +815,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");
@ -783,29 +826,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;
@ -860,14 +906,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;
}
@ -983,7 +1024,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.
@ -1141,7 +1182,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);
@ -1188,7 +1229,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);
@ -1246,7 +1287,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

@ -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 <filesystem>
@ -36,6 +36,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"
@ -96,7 +97,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();
}
@ -527,7 +528,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

@ -39,11 +39,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

@ -364,7 +364,7 @@ void EmitContext::DefineInputs() {
}
break;
}
case LogicalStage::Fragment:
case LogicalStage::Fragment: {
if (info.loads.GetAny(IR::Attribute::FragCoord)) {
frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input);
}
@ -418,7 +418,13 @@ void EmitContext::DefineInputs() {
spv::StorageClass::Input);
}
}
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
const bool has_clip_distance_inputs = runtime_info.fs_info.clip_distance_emulation;
// Clip distances attribute vector is the last in inputs array
const auto num_inputs =
runtime_info.fs_info.num_inputs - (has_clip_distance_inputs ? 1 : 0);
for (s32 i = 0; i < num_inputs; i++) {
const auto& input = runtime_info.fs_info.inputs[i];
if (input.IsDefault()) {
continue;
@ -428,12 +434,13 @@ void EmitContext::DefineInputs() {
const auto [primary, auxiliary] = info.fs_interpolation[i];
const Id type = F32[num_components];
const Id attr_id = [&] {
const auto bind_location = input.param_index + (has_clip_distance_inputs ? 1 : 0);
if (primary == Qualifier::PerVertex &&
profile.supports_fragment_shader_barycentric) {
return Name(DefineInput(TypeArray(type, ConstU32(3U)), input.param_index),
return Name(DefineInput(TypeArray(type, ConstU32(3U)), bind_location),
fmt::format("fs_in_attr{}_p", i));
}
return Name(DefineInput(type, input.param_index), fmt::format("fs_in_attr{}", i));
return Name(DefineInput(type, bind_location), fmt::format("fs_in_attr{}", i));
}();
if (primary == Qualifier::PerVertex) {
Decorate(attr_id, profile.supports_amd_shader_explicit_vertex_parameter
@ -450,7 +457,15 @@ void EmitContext::DefineInputs() {
input_params[i] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components,
false, false, primary == Qualifier::PerVertex);
}
if (has_clip_distance_inputs) {
const auto type = F32[MaxEmulatedClipDistances];
const auto attr_id = Name(DefineInput(type, 0), fmt::format("cldist_attr{}", 0));
input_params[num_inputs] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id,
MaxEmulatedClipDistances, false);
}
break;
}
case LogicalStage::Compute:
if (info.loads.GetAny(IR::Attribute::WorkgroupIndex) ||
info.loads.GetAny(IR::Attribute::WorkgroupId)) {
@ -546,11 +561,16 @@ void EmitContext::DefineVertexBlock() {
const std::array<Id, 8> zero{f32_zero_value, f32_zero_value, f32_zero_value, f32_zero_value,
f32_zero_value, f32_zero_value, f32_zero_value, f32_zero_value};
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
if (info.stores.GetAny(IR::Attribute::ClipDistance)) {
const Id type{TypeArray(F32[1], ConstU32(8U))};
const Id initializer{ConstantComposite(type, zero)};
clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance, spv::StorageClass::Output,
initializer);
const bool needs_clip_distance_emulation = l_stage == LogicalStage::Vertex &&
stage == Stage::Vertex &&
profile.needs_clip_distance_emulation;
if (!needs_clip_distance_emulation) {
if (info.stores.GetAny(IR::Attribute::ClipDistance)) {
const Id type{TypeArray(F32[1], ConstU32(8U))};
const Id initializer{ConstantComposite(type, zero)};
clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance,
spv::StorageClass::Output, initializer);
}
}
if (info.stores.GetAny(IR::Attribute::CullDistance)) {
const Id type{TypeArray(F32[1], ConstU32(8U))};
@ -583,16 +603,27 @@ void EmitContext::DefineOutputs() {
Name(output_attr_array, "out_attrs");
}
} else {
const auto has_clip_distance_outputs = info.stores.GetAny(IR::Attribute::ClipDistance);
u32 num_attrs = 0u;
for (u32 i = 0; i < IR::NumParams; i++) {
const IR::Attribute param{IR::Attribute::Param0 + i};
if (!info.stores.GetAny(param)) {
continue;
}
const u32 num_components = info.stores.NumComponents(param);
const Id id{DefineOutput(F32[num_components], i)};
const Id id{
DefineOutput(F32[num_components], i + (has_clip_distance_outputs ? 1 : 0))};
Name(id, fmt::format("out_attr{}", i));
output_params[i] =
GetAttributeInfo(AmdGpu::NumberFormat::Float, id, num_components, true);
++num_attrs;
}
if (has_clip_distance_outputs) {
clip_distances = Id{DefineOutput(F32[MaxEmulatedClipDistances], 0)};
output_params[num_attrs] = GetAttributeInfo(
AmdGpu::NumberFormat::Float, clip_distances, MaxEmulatedClipDistances, true);
Name(clip_distances, fmt::format("cldist_attr{}", 0));
}
}
break;

View File

@ -219,6 +219,7 @@ public:
void V_NOT_B32(const GcnInst& inst);
void V_BFREV_B32(const GcnInst& inst);
void V_FFBH_U32(const GcnInst& inst);
void V_FFBH_I32(const GcnInst& inst);
void V_FFBL_B32(const GcnInst& inst);
void V_FREXP_EXP_I32_F64(const GcnInst& inst);
void V_FREXP_MANT_F64(const GcnInst& inst);
@ -231,6 +232,7 @@ public:
// VOPC
void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_CMP_F64(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_CMP_CLASS_F32(const GcnInst& inst);
@ -259,6 +261,7 @@ public:
void V_CVT_PK_I16_I32(const GcnInst& inst);
void V_CVT_PK_U8_F32(const GcnInst& inst);
void V_LSHL_B64(const GcnInst& inst);
void V_LSHR_B64(const GcnInst& inst);
void V_ALIGNBIT_B32(const GcnInst& inst);
void V_ALIGNBYTE_B32(const GcnInst& inst);
void V_MUL_F64(const GcnInst& inst);

View File

@ -188,6 +188,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_FFBH_U32(inst);
case Opcode::V_FFBL_B32:
return V_FFBL_B32(inst);
case Opcode::V_FFBH_I32:
return V_FFBH_I32(inst);
case Opcode::V_FREXP_EXP_I32_F64:
return V_FREXP_EXP_I32_F64(inst);
case Opcode::V_FREXP_MANT_F64:
@ -264,6 +266,34 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
case Opcode::V_CMPX_TRU_F32:
return V_CMP_F32(ConditionOp::TRU, true, inst);
// V_CMP_{OP16}_F64
case Opcode::V_CMP_F_F64:
return V_CMP_F64(ConditionOp::F, false, inst);
case Opcode::V_CMP_LT_F64:
return V_CMP_F64(ConditionOp::LT, false, inst);
case Opcode::V_CMP_EQ_F64:
return V_CMP_F64(ConditionOp::EQ, false, inst);
case Opcode::V_CMP_LE_F64:
return V_CMP_F64(ConditionOp::LE, false, inst);
case Opcode::V_CMP_GT_F64:
return V_CMP_F64(ConditionOp::GT, false, inst);
case Opcode::V_CMP_LG_F64:
return V_CMP_F64(ConditionOp::LG, false, inst);
case Opcode::V_CMP_GE_F64:
return V_CMP_F64(ConditionOp::GE, false, inst);
case Opcode::V_CMP_U_F64:
return V_CMP_F64(ConditionOp::U, false, inst);
case Opcode::V_CMP_NGE_F64:
return V_CMP_F64(ConditionOp::LT, false, inst);
case Opcode::V_CMP_NGT_F64:
return V_CMP_F64(ConditionOp::LE, false, inst);
case Opcode::V_CMP_NLE_F64:
return V_CMP_F64(ConditionOp::GT, false, inst);
case Opcode::V_CMP_NEQ_F64:
return V_CMP_F64(ConditionOp::LG, false, inst);
case Opcode::V_CMP_NLT_F64:
return V_CMP_F64(ConditionOp::GE, false, inst);
// V_CMP_{OP8}_I32
case Opcode::V_CMP_LT_I32:
return V_CMP_U32(ConditionOp::LT, true, false, inst);
@ -394,6 +424,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_CVT_PK_U8_F32(inst);
case Opcode::V_LSHL_B64:
return V_LSHL_B64(inst);
case Opcode::V_LSHR_B64:
return V_LSHR_B64(inst);
case Opcode::V_ADD_F64:
return V_ADD_F64(inst);
case Opcode::V_ALIGNBIT_B32:
@ -918,6 +950,19 @@ void Translator::V_FFBL_B32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.FindILsb(src0));
}
void Translator::V_FFBH_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
// Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB)
// position
const IR::U32 msb_pos = ir.FindSMsb(src0);
const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos);
// Select 0xFFFFFFFF if src0 was 0 or -1
const IR::U32 minusOne = ir.Imm32(~0U);
const IR::U1 cond =
ir.LogicalAnd(ir.INotEqual(src0, ir.Imm32(0)), ir.INotEqual(src0, minusOne));
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, minusOne)});
}
void Translator::V_FREXP_EXP_I32_F64(const GcnInst& inst) {
const IR::F64 src0{GetSrc64<IR::F64>(inst.src[0])};
SetDst(inst.dst[0], ir.FPFrexpExp(src0));
@ -1011,6 +1056,47 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) {
}
}
void Translator::V_CMP_F64(ConditionOp op, bool set_exec, const GcnInst& inst) {
const IR::F64 src0{GetSrc64<IR::F64>(inst.src[0])};
const IR::F64 src1{GetSrc64<IR::F64>(inst.src[1])};
const IR::U1 result = [&] {
switch (op) {
case ConditionOp::F:
return ir.Imm1(false);
case ConditionOp::EQ:
return ir.FPEqual(src0, src1);
case ConditionOp::LG:
return ir.FPNotEqual(src0, src1);
case ConditionOp::GT:
return ir.FPGreaterThan(src0, src1);
case ConditionOp::LT:
return ir.FPLessThan(src0, src1);
case ConditionOp::LE:
return ir.FPLessThanEqual(src0, src1);
case ConditionOp::GE:
return ir.FPGreaterThanEqual(src0, src1);
case ConditionOp::U:
return ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1));
default:
UNREACHABLE();
}
}();
if (set_exec) {
ir.SetExec(result);
}
switch (inst.dst[1].field) {
case OperandField::VccLo:
ir.SetVcc(result);
break;
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result);
break;
default:
UNREACHABLE();
}
}
void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
@ -1357,6 +1443,12 @@ void Translator::V_LSHL_B64(const GcnInst& inst) {
SetDst64(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm64(u64(0x3F)))));
}
void Translator::V_LSHR_B64(const GcnInst& inst) {
const IR::U64 src0{GetSrc64(inst.src[0])};
const IR::U64 src1{GetSrc64(inst.src[1])};
SetDst64(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm64(u64(0x3F)))));
}
void Translator::V_ALIGNBIT_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};

View File

@ -101,7 +101,7 @@ std::string NameOf(Attribute attribute) {
case Attribute::Param31:
return "Param31";
case Attribute::ClipDistance:
return "ClipDistanace";
return "ClipDistance";
case Attribute::CullDistance:
return "CullDistance";
case Attribute::RenderTargetIndex:

View File

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/basic_block.h"
#include "shader_recompiler/ir/ir_emitter.h"
#include "shader_recompiler/ir/program.h"
namespace Shader {
void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info) {
auto& info = runtime_info.fs_info;
if (!info.clip_distance_emulation || program.info.l_stage != LogicalStage::Fragment) {
return;
}
auto* first_block = *program.blocks.begin();
auto it = std::ranges::find_if(first_block->Instructions(), [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::Opcode::Prologue;
});
ASSERT(it != first_block->end());
++it;
ASSERT(it != first_block->end());
++it;
IR::IREmitter ir{*first_block, it};
// We don't know how many clip distances are exported by VS as it is not processed at this point
// yet. Here is an assumption that we will have not more than 4 of them (while max is 8) to save
// one attributes export slot.
const auto attrib = IR::Attribute::Param0 + info.num_inputs;
for (u32 comp = 0; comp < MaxEmulatedClipDistances; ++comp) {
const auto attr_read = ir.GetAttribute(attrib, comp);
const auto cond_id = ir.FPLessThan(attr_read, ir.Imm32(0.0f));
ir.Discard(cond_id);
}
++info.num_inputs;
}
} // namespace Shader

View File

@ -8,7 +8,8 @@
namespace Shader {
struct Profile;
}
void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info);
} // namespace Shader
namespace Shader::Optimization {

View File

@ -41,7 +41,7 @@ struct Profile {
bool needs_lds_barriers{};
bool needs_buffer_offsets{};
bool needs_unorm_fixup{};
bool _pad0{};
bool needs_clip_distance_emulation{};
};
} // namespace Shader

View File

@ -13,17 +13,16 @@ namespace Shader {
IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) {
size_t num_syntax_blocks{};
for (const auto& node : syntax_list) {
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
for (const auto& [_, type] : syntax_list) {
if (type == IR::AbstractSyntaxNode::Type::Block) {
++num_syntax_blocks;
}
}
IR::BlockList blocks;
IR::BlockList blocks{};
blocks.reserve(num_syntax_blocks);
u32 order_index{};
for (const auto& node : syntax_list) {
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
blocks.push_back(node.data.block);
for (const auto& [data, type] : syntax_list) {
if (type == IR::AbstractSyntaxNode::Type::Block) {
blocks.push_back(data.block);
}
}
return blocks;
@ -60,6 +59,10 @@ IR::Program TranslateProgram(const std::span<const u32>& code, Pools& pools, Inf
program.blocks = GenerateBlocks(program.syntax_list);
program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front());
// On NVIDIA GPUs HW interpolation of clip distance values seems broken, and we need to emulate
// it with expensive discard in PS.
Shader::InjectClipDistanceAttributes(program, runtime_info);
// Run optimization passes
if (!profile.support_float64) {
Shader::Optimization::LowerFp64ToFp32(program);

View File

@ -34,6 +34,7 @@ enum class LogicalStage : u32 {
};
constexpr u32 MaxStageTypes = static_cast<u32>(LogicalStage::NumLogicalStages);
constexpr auto MaxEmulatedClipDistances = 4u;
constexpr Stage StageFromIndex(size_t index) noexcept {
return static_cast<Stage>(index);
@ -201,14 +202,16 @@ struct FragmentRuntimeInfo {
std::array<PsInput, 32> inputs;
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
AmdGpu::ShaderExportFormat z_export_format;
u8 mrtz_mask;
bool dual_source_blending;
u8 mrtz_mask{};
bool dual_source_blending{false};
bool clip_distance_emulation{false};
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
return std::ranges::equal(color_buffers, other.color_buffers) &&
en_flags == other.en_flags && addr_flags == other.addr_flags &&
num_inputs == other.num_inputs && z_export_format == other.z_export_format &&
mrtz_mask == other.mrtz_mask && dual_source_blending == other.dual_source_blending &&
clip_distance_emulation == other.clip_distance_emulation &&
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
other.inputs.begin() + num_inputs);
}

View File

@ -101,7 +101,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS
switch (stage) {
case Stage::Local: {
BuildCommon(regs.ls_program);
Shader::TessellationDataConstantBuffer tess_constants;
Shader::TessellationDataConstantBuffer tess_constants{};
const auto* hull_info = infos[u32(Shader::LogicalStage::TessellationControl)];
hull_info->ReadTessConstantBuffer(tess_constants);
info.ls_info.ls_stride = tess_constants.ls_stride;
@ -199,6 +199,10 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS
for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
info.fs_info.color_buffers[i] = graphics_key.color_buffers[i];
}
info.fs_info.clip_distance_emulation =
regs.vs_output_control.clip_distance_enable &&
!regs.stage_enable.IsStageEnabled(static_cast<u32>(Stage::Local)) &&
profile.needs_clip_distance_emulation;
break;
}
case Stage::Compute: {
@ -266,6 +270,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
instance.GetDriverID() == vk::DriverId::eMoltenvk,
.needs_buffer_offsets = instance.StorageMinAlignment() > 4,
.needs_unorm_fixup = instance.GetDriverID() == vk::DriverId::eMoltenvk,
.needs_clip_distance_emulation = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary,
};
WarmUp();
@ -460,7 +465,13 @@ bool PipelineCache::RefreshGraphicsStages() {
infos.fill(nullptr);
modules.fill(nullptr);
bind_stage(Stage::Fragment, LogicalStage::Fragment);
const auto result = bind_stage(Stage::Fragment, LogicalStage::Fragment);
if (!result && regs.vs_output_control.clip_distance_enable &&
profile.needs_clip_distance_emulation) {
// TODO: need to implement a discard only fallback shader
LOG_WARNING(Render_Vulkan,
"Clip distance emulation is ineffective due to absense of fragment shader");
}
const auto* fs_info = infos[static_cast<u32>(LogicalStage::Fragment)];
key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u;