mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-06-25 22:35:02 -06:00
Merge branch 'main' into gr2fix
This commit is contained in:
commit
4c13a797fb
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -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
15
.gitmodules
vendored
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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",
|
||||
|
||||
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
51
documents/Docker Builder/.devcontainer/devcontainer.json
Normal 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"
|
||||
}
|
||||
}
|
||||
45
documents/Docker Builder/.docker/Dockerfile
Normal file
45
documents/Docker Builder/.docker/Dockerfile
Normal 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
|
||||
10
documents/Docker Builder/docker-compose.yml
Normal file
10
documents/Docker Builder/docker-compose.yml
Normal 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
|
||||
BIN
documents/Screenshots/Windows/vscode-ext-1.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
documents/Screenshots/Windows/vscode-ext-2.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/Screenshots/Windows/vscode-ext-3.png
Normal file
BIN
documents/Screenshots/Windows/vscode-ext-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
100
documents/building-docker.md
Normal file
100
documents/building-docker.md
Normal 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 don’t 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.
|
||||
@ -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._
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 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
1
externals/CLI11
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit bf5a16a26a34a9a7ad75f4a7705585e44675fef0
|
||||
3
externals/CMakeLists.txt
vendored
3
externals/CMakeLists.txt
vendored
@ -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
2
externals/MoltenVK
vendored
@ -1 +1 @@
|
||||
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
|
||||
Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030
|
||||
1
externals/ext-CLI11
vendored
1
externals/ext-CLI11
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 1cce1483345e60997b87720948c37d6a34db2658
|
||||
@ -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) \
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
266
src/core/libraries/libpng/pngenc.cpp
Normal file
266
src/core/libraries/libpng/pngenc.cpp
Normal 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
|
||||
67
src/core/libraries/libpng/pngenc.h
Normal file
67
src/core/libraries/libpng/pngenc.h
Normal 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
|
||||
14
src/core/libraries/libpng/pngenc_error.h
Normal file
14
src/core/libraries/libpng/pngenc_error.h
Normal 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;
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
812
src/core/libraries/np/np_matching2.cpp
Normal file
812
src/core/libraries/np/np_matching2.cpp
Normal 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
|
||||
48
src/core/libraries/np/np_matching2.h
Normal file
48
src/core/libraries/np/np_matching2.h
Normal 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
|
||||
83
src/core/libraries/np/np_partner.cpp
Normal file
83
src/core/libraries/np/np_partner.cpp
Normal 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
|
||||
15
src/core/libraries/np/np_partner.h
Normal file
15
src/core/libraries/np/np_partner.h
Normal 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
|
||||
9
src/core/libraries/np/np_partner_error.h
Normal file
9
src/core/libraries/np/np_partner_error.h
Normal 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;
|
||||
1002
src/core/libraries/np/np_tus.cpp
Normal file
1002
src/core/libraries/np/np_tus.cpp
Normal file
File diff suppressed because it is too large
Load Diff
158
src/core/libraries/np/np_tus.h
Normal file
158
src/core/libraries/np/np_tus.h
Normal 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
|
||||
@ -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
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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{};
|
||||
|
||||
@ -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},
|
||||
|
||||
11
src/main.cpp
11
src/main.cpp
@ -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"};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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])};
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
@ -8,7 +8,8 @@
|
||||
|
||||
namespace Shader {
|
||||
struct Profile;
|
||||
}
|
||||
void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
} // namespace Shader
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user