Merge branch 'main' into ime-fixes-again

This commit is contained in:
Valdis Bogdāns 2026-02-01 22:24:24 +02:00 committed by GitHub
commit be4c29a936
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1546 additions and 28 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

@ -526,6 +526,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
@ -583,6 +586,8 @@ 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

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",

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

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

@ -274,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)

1
externals/ext-CLI11 vendored

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

View File

@ -107,6 +107,7 @@ 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) \

View File

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

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

@ -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,6 +35,7 @@
#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"
@ -99,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);

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

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

@ -177,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);
@ -219,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);

View File

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